feat: externalized annotations (external mode) + --stdout flag#6
Merged
Merged
Conversation
* 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>
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
approved these changes
Apr 24, 2026
1fcbfee
into
Bugb-Technologies:main
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.
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
feat: externalized annotations support in .guardlink/annotations/**.gal
Type
Checklist
npm run buildpassesnpm testpassesguardlink validate .passes (if annotations changed)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"