Skip to content

feat: externalized annotations (external mode) + --stdout flag#6

Merged
Animesh-Sri-bugb merged 1 commit into
Bugb-Technologies:mainfrom
jordi-murgo:main
Apr 24, 2026
Merged

feat: externalized annotations (external mode) + --stdout flag#6
Animesh-Sri-bugb merged 1 commit into
Bugb-Technologies:mainfrom
jordi-murgo:main

Conversation

@jordi-murgo

@jordi-murgo jordi-murgo commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

feat: externalized annotations support in .guardlink/annotations/**.gal

  • guardlink init --mode external: restricts all writes to .guardlink/ (no agent files, no root .mcp.json, no docs/; reference doc and mcp config template placed inside .guardlink/ instead)
  • guardlink annotate --stdout: prints prompt to stdout for piping, skips clipboard and all console output
  • MCP guardlink_annotate: mode enum updated to inline|external
  • TUI /annotate: help text updated
  • Tests: agents and prompts test suites covering new mode values
  • --mode inline/external annotation mode, --stdout flag for annotate

Type

  • Bug fix
  • New feature
  • Annotation spec change
  • Documentation
  • CI / tooling

Checklist

  • npm run build passes
  • npm test passes
  • guardlink validate . passes (if annotations changed)
  • CHANGELOG.md updated (for user-facing changes)

Spec changes

Suport for external annotations inside .guardlink/annotations/ with @source support:

$ cat .guardlink/annotations/src/core/agent/core_agent.py.gal

@source file:src/core/agent/core_agent.py line:108 symbol:invoke
@Handles secrets on fwk.agent -- "base_metadata includes system_prompt in plaintext; this dict is forwarded to observability middleware and ultimately to Langfuse"
@exposes fwk.agent to #obs-leakage [P2] cwe:CWE-359 -- "system_prompt stored in ctx.metadata['system_prompt'] flows to ObservabilityMiddleware → Langfuse SaaS; system prompts may contain internal instructions"
@comment -- "invocation_id is derived from task_id → chat_id → uuid4(); caller-supplied task_id/chat_id accepted without validation — ensure these are not used for authorization decisions downstream"

* feat: externalized annotations support — parser, review, types, tests

- Parser: support .gal files with @source blocks, comment-strip improvements,
  parse-file handles external annotation files, parse-project scans .guardlink/annotations/
- Review: improved exposure formatting and reviewable exposure types
- Types: new fields for externalized annotation tracking
- Diff: git diff integration improvements
- Templates: updated agent instruction content for external mode
- Tests: parser, diff, review test suites
- Docs: SPEC and GUARDLINK_REFERENCE updated for external mode
- Agent files: sync with updated threat model context
- Bump package version

* feat: inline/external annotation mode, --stdout flag for annotate

- Rename annotation mode 'gal' → 'external' (clean break, no alias)
- guardlink init --mode external: restricts all writes to .guardlink/
  (no agent files, no root .mcp.json, no docs/; reference doc and mcp
  config template placed inside .guardlink/ instead)
- guardlink annotate --stdout: prints prompt to stdout for piping,
  skips clipboard and all console output
- MCP guardlink_annotate: mode enum updated to inline|external
- TUI /annotate: help text updated
- Tests: agents and prompts test suites covering new mode values

---------

Co-authored-by: jpmo <jordi.murgo@gft.com>
@Animesh-Sri-bugb

Copy link
Copy Markdown
Contributor

Ran this end-to-end locally, external mode works cleanly. Thanks for the thorough work. Curious what workflow led you to build this, always interested in how GuardLink is actually being used. Happy to continue the conversation in GitHub Discussions if you'd prefer to go deeper, or over email (its in my profile).

@Animesh-Sri-bugb Animesh-Sri-bugb merged commit 1fcbfee into Bugb-Technologies:main Apr 24, 2026
0 of 3 checks passed
Animesh-Sri-bugb added a commit that referenced this pull request Apr 24, 2026
Ships PR #6 polish: external annotations mode + --stdout, plus the
shield wrap fix, version bump to 1.4.2, documentation corrections,
and lockfile cleanup.

See CHANGELOG.md for the full 1.4.2 entry.
Animesh-Sri-bugb added a commit that referenced this pull request May 12, 2026
The pentest template loader used loose regexes that matched 'id' and
'severity' as substrings inside other words:

  /id[:\s]*["']?([a-z0-9_-]+)["']?/i
  /severity[:\s]*["']?(critical|high|medium|low|info)["']?/i

In a Python template containing the word 'bridge' (b-r-id-g-e), the id
regex matched 'id' as a substring and captured 'ge'. In a template
containing 'guide', it captured 'e'. The dashboard rendered these as
the template card titles — visible as the 'ge' and 'e' labels in the
Pentest Findings tab.

Severity always defaulted to 'medium' because Python templates use
`severity = "critical"` (equals separator) while the regex only
allowed [:\s]* between the field name and the value, so the assignment
form never matched.

New regexes anchor on a complete field name with optional surrounding
quotes (for JSON-style "id": "x") and require either : or = as the
separator before a quoted value:

  /["']?(?:template_)?id["']?\s*[:=]\s*["']([a-z0-9_-]+)["']/i
  /["']?severity["']?\s*[:=]\s*["']?(critical|high|medium|low|info)["']?/i

Adds tests/pentest-loader.test.ts (10 tests) covering JSON, Python,
and YAML conventions plus defensive cases for substring false matches.

Verified live against the user's Juice Shop session — template cards
now show 'login-sqli-network' / 'login-sqli-source-audit' with
'critical' severity instead of 'ge'/'e' with 'medium'.

Fixes punch-list bugs #6 and #8.
Animesh-Sri-bugb added a commit that referenced this pull request May 12, 2026
All ParseDiagnostic records have historically carried level: 'error'
or 'warning' and the CLI treats both the same way — print and
continue. There is no notion of a diagnostic so severe that the
consumer should abort rather than render a partial threat model.
Today this is benign because the parser only ever emits per-
annotation errors, which are legitimately skip-and-continue. But the
type system has no vocabulary for cases where the whole model is
unsafe — schema version mismatch on a saved report, definitions.ts
entirely unparseable, structurally invalid input — and v1.6 work in
that direction has nowhere clean to land.

This commit adds 'fatal' to the level union as reserved vocabulary.
No code path emits a fatal diagnostic today; this is a non-breaking
type widening intended purely so v1.6 can introduce the first
emission site without a coordinated cross-file change.

Implementation:

- src/types/index.ts: ParseDiagnostic.level extends from
  'error' | 'warning' to 'error' | 'warning' | 'fatal' with detailed
  JSDoc explaining each tier's semantics and consumer obligation. A
  TODO(fatal-tier) note enumerates the audit work required before
  the first fatal emission lands: every d.level === 'error' filter
  across the codebase (8 in cli/index.ts, 2 in tui/commands.ts, 1 in
  mcp/server.ts) currently silently drops fatals because of how
  narrowing works against the wider union. Those filters need to
  become 'error' || 'fatal' before any code path produces a fatal,
  otherwise fatals would bypass the existing exit-1 / abort logic.

- src/parser/format.ts (new): diagnosticIcon(level) pure helper
  returning the icon character for each level. Exhaustive switch
  means TypeScript will flag this function if a future iteration
  adds a fourth level without updating the mapping. Icons: ✗✗ for
  fatal (visually distinct from error), ✗ for error, ⚠ for warning.

- src/cli/index.ts printDiagnostics(): uses diagnosticIcon() instead
  of the inline ternary, so fatals would render with the distinct
  icon if any code path emitted them. Summary line gains an optional
  fatals count, only printed when non-zero so today's output is
  unchanged: '2 error(s), 0 warning(s)' for an error-only run, but
  '1 fatal(s), 0 error(s), 0 warning(s)' if a fatal ever arrives.

- src/tui/commands.ts status command: same icon helper, wrapped in
  C.error() color for fatal + error and C.warn() for warning. Two-
  space indent and color treatment preserved exactly.

- tests/diagnostics.test.ts (new, 7 tests): asserts the three icons
  are correct and distinct, plus three compile-time type assertions
  constructing ParseDiagnostic records of each level — if the union
  ever silently loses a member, this test file fails to build.

What this fix does NOT do:

- Identify or promote any existing 'error' condition to 'fatal'.
  Option 1 of the v1.5.1 punch-list discussion explicitly said
  vocabulary-only, no behavior change. Promoting silent-continue
  conditions to fatal is a behavior change that needs more thought
  than a bug-fix release warrants. The TODO note captures the audit
  obligation so the v1.6 implementer can do it deliberately.

- Audit the 11 'd.level === "error"' filter sites mentioned above
  to also accept fatal. They still work — fatals just get silently
  dropped today, which is fine because nothing emits them. Auditing
  is a v1.6 task gated on the first emission.

Verified end-to-end against the Juice Shop fixture: existing two-
error rendering is byte-identical to before the change ('✗' icon,
'2 error(s), 0 warning(s)' summary, no fatal count shown). Build
clean. 140/140 tests pass.

Addresses punch-list bug #6.
Animesh-Sri-bugb added a commit that referenced this pull request May 12, 2026
Reconcile version references across the project to 1.4.3, the agreed
target for the v1.5.1-deferred bug-fix batch on the feat/v1.5.0 branch.

Touched:
- package.json: 1.4.1 -> 1.4.3
- package-lock.json: 1.4.1 -> 1.4.3 (root + packages[''])
- src/cli/index.ts: program.version('1.4.1') -> '1.4.3'
- src/mcp/server.ts: McpServer version '1.4.0' -> '1.4.3'

The MCP server was inconsistently at 1.4.0 even when other surfaces
reported 1.4.1; reconciling all four to 1.4.3 closes that gap.

Scope rationale (from the v1.5.1 discussion): the work on this branch
is materially bug-fix oriented — confidence rendering (#7), topology
dedup (#9), prompt.md migration (#14), fatal tier reservation (#6),
JWT redaction opt-in (#11) — even though two additive features
landed alongside (multi-hop @flows chains, quoted refs in #5). Patch
bump rather than minor reflects the intent: this is the v1.4.x line
plus tight fixes, not a v1.5 product cut. The minor bump and broader
release notes will happen at the rebase against main and the formal
v1.5.0 cut.

Verified: 'guardlink --version' prints 1.4.3; npm build clean;
167/167 tests pass.

Fixes punch-list bug #12.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants