guardlink 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +111 -7
  2. package/README.md +53 -5
  3. package/dist/agents/config.d.ts +7 -0
  4. package/dist/agents/config.d.ts.map +1 -1
  5. package/dist/agents/config.js.map +1 -1
  6. package/dist/agents/index.d.ts +9 -1
  7. package/dist/agents/index.d.ts.map +1 -1
  8. package/dist/agents/index.js +36 -1
  9. package/dist/agents/index.js.map +1 -1
  10. package/dist/agents/launcher.d.ts.map +1 -1
  11. package/dist/agents/launcher.js +5 -0
  12. package/dist/agents/launcher.js.map +1 -1
  13. package/dist/agents/prompts.d.ts +16 -1
  14. package/dist/agents/prompts.d.ts.map +1 -1
  15. package/dist/agents/prompts.js +511 -16
  16. package/dist/agents/prompts.js.map +1 -1
  17. package/dist/analyze/format.d.ts +72 -0
  18. package/dist/analyze/format.d.ts.map +1 -0
  19. package/dist/analyze/format.js +176 -0
  20. package/dist/analyze/format.js.map +1 -0
  21. package/dist/analyze/index.d.ts +76 -0
  22. package/dist/analyze/index.d.ts.map +1 -1
  23. package/dist/analyze/index.js +165 -2
  24. package/dist/analyze/index.js.map +1 -1
  25. package/dist/analyze/prompts.d.ts +3 -2
  26. package/dist/analyze/prompts.d.ts.map +1 -1
  27. package/dist/analyze/prompts.js +17 -3
  28. package/dist/analyze/prompts.js.map +1 -1
  29. package/dist/analyzer/sarif.d.ts +3 -2
  30. package/dist/analyzer/sarif.d.ts.map +1 -1
  31. package/dist/analyzer/sarif.js +29 -3
  32. package/dist/analyzer/sarif.js.map +1 -1
  33. package/dist/cli/index.d.ts +2 -0
  34. package/dist/cli/index.d.ts.map +1 -1
  35. package/dist/cli/index.js +408 -37
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/dashboard/data.d.ts +11 -0
  38. package/dist/dashboard/data.d.ts.map +1 -1
  39. package/dist/dashboard/data.js +12 -0
  40. package/dist/dashboard/data.js.map +1 -1
  41. package/dist/dashboard/diagrams.d.ts +81 -12
  42. package/dist/dashboard/diagrams.d.ts.map +1 -1
  43. package/dist/dashboard/diagrams.js +750 -362
  44. package/dist/dashboard/diagrams.js.map +1 -1
  45. package/dist/dashboard/generate.d.ts +5 -2
  46. package/dist/dashboard/generate.d.ts.map +1 -1
  47. package/dist/dashboard/generate.js +2516 -244
  48. package/dist/dashboard/generate.js.map +1 -1
  49. package/dist/diff/engine.d.ts +2 -1
  50. package/dist/diff/engine.d.ts.map +1 -1
  51. package/dist/diff/engine.js +3 -2
  52. package/dist/diff/engine.js.map +1 -1
  53. package/dist/diff/git.js +3 -3
  54. package/dist/diff/git.js.map +1 -1
  55. package/dist/init/index.d.ts +7 -0
  56. package/dist/init/index.d.ts.map +1 -1
  57. package/dist/init/index.js +82 -27
  58. package/dist/init/index.js.map +1 -1
  59. package/dist/init/migrate.d.ts +39 -0
  60. package/dist/init/migrate.d.ts.map +1 -0
  61. package/dist/init/migrate.js +45 -0
  62. package/dist/init/migrate.js.map +1 -0
  63. package/dist/init/templates.d.ts +8 -0
  64. package/dist/init/templates.d.ts.map +1 -1
  65. package/dist/init/templates.js +68 -6
  66. package/dist/init/templates.js.map +1 -1
  67. package/dist/mcp/lookup.d.ts +1 -0
  68. package/dist/mcp/lookup.d.ts.map +1 -1
  69. package/dist/mcp/lookup.js +138 -10
  70. package/dist/mcp/lookup.js.map +1 -1
  71. package/dist/mcp/server.d.ts +2 -1
  72. package/dist/mcp/server.d.ts.map +1 -1
  73. package/dist/mcp/server.js +32 -15
  74. package/dist/mcp/server.js.map +1 -1
  75. package/dist/parser/clear.d.ts +2 -1
  76. package/dist/parser/clear.d.ts.map +1 -1
  77. package/dist/parser/clear.js +19 -29
  78. package/dist/parser/clear.js.map +1 -1
  79. package/dist/parser/comment-strip.d.ts +5 -0
  80. package/dist/parser/comment-strip.d.ts.map +1 -1
  81. package/dist/parser/comment-strip.js +8 -0
  82. package/dist/parser/comment-strip.js.map +1 -1
  83. package/dist/parser/feature-filter.d.ts +42 -0
  84. package/dist/parser/feature-filter.d.ts.map +1 -0
  85. package/dist/parser/feature-filter.js +109 -0
  86. package/dist/parser/feature-filter.js.map +1 -0
  87. package/dist/parser/format.d.ts +24 -0
  88. package/dist/parser/format.d.ts.map +1 -0
  89. package/dist/parser/format.js +29 -0
  90. package/dist/parser/format.js.map +1 -0
  91. package/dist/parser/index.d.ts +2 -0
  92. package/dist/parser/index.d.ts.map +1 -1
  93. package/dist/parser/index.js +1 -0
  94. package/dist/parser/index.js.map +1 -1
  95. package/dist/parser/parse-file.d.ts +1 -0
  96. package/dist/parser/parse-file.d.ts.map +1 -1
  97. package/dist/parser/parse-file.js +34 -9
  98. package/dist/parser/parse-file.js.map +1 -1
  99. package/dist/parser/parse-line.d.ts +9 -0
  100. package/dist/parser/parse-line.d.ts.map +1 -1
  101. package/dist/parser/parse-line.js +100 -26
  102. package/dist/parser/parse-line.js.map +1 -1
  103. package/dist/parser/parse-project.d.ts +1 -0
  104. package/dist/parser/parse-project.d.ts.map +1 -1
  105. package/dist/parser/parse-project.js +36 -2
  106. package/dist/parser/parse-project.js.map +1 -1
  107. package/dist/parser/validate.d.ts +3 -0
  108. package/dist/parser/validate.d.ts.map +1 -1
  109. package/dist/parser/validate.js +7 -0
  110. package/dist/parser/validate.js.map +1 -1
  111. package/dist/report/index.d.ts +1 -0
  112. package/dist/report/index.d.ts.map +1 -1
  113. package/dist/report/index.js +1 -0
  114. package/dist/report/index.js.map +1 -1
  115. package/dist/report/report.d.ts.map +1 -1
  116. package/dist/report/report.js +924 -24
  117. package/dist/report/report.js.map +1 -1
  118. package/dist/report/sequence.d.ts +11 -0
  119. package/dist/report/sequence.d.ts.map +1 -0
  120. package/dist/report/sequence.js +140 -0
  121. package/dist/report/sequence.js.map +1 -0
  122. package/dist/review/index.d.ts +3 -1
  123. package/dist/review/index.d.ts.map +1 -1
  124. package/dist/review/index.js +77 -35
  125. package/dist/review/index.js.map +1 -1
  126. package/dist/tui/commands.d.ts +1 -0
  127. package/dist/tui/commands.d.ts.map +1 -1
  128. package/dist/tui/commands.js +98 -12
  129. package/dist/tui/commands.js.map +1 -1
  130. package/dist/tui/index.d.ts.map +1 -1
  131. package/dist/tui/index.js +7 -2
  132. package/dist/tui/index.js.map +1 -1
  133. package/dist/types/index.d.ts +59 -3
  134. package/dist/types/index.d.ts.map +1 -1
  135. package/dist/workspace/merge.d.ts.map +1 -1
  136. package/dist/workspace/merge.js +6 -2
  137. package/dist/workspace/merge.js.map +1 -1
  138. package/package.json +1 -1
@@ -22,17 +22,18 @@
22
22
  */
23
23
  import { resolve, basename } from 'node:path';
24
24
  import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
25
- import { parseProject, findDanglingRefs, findUnmitigatedExposures, findAcceptedWithoutAudit, findAcceptedExposures, clearAnnotations } from '../parser/index.js';
25
+ import { parseProject, findDanglingRefs, findUnmitigatedExposures, findAcceptedWithoutAudit, findAcceptedExposures, clearAnnotations, listFeatures, filterByFeature, getFeatureSummaries } from '../parser/index.js';
26
26
  import { initProject, detectProject, promptAgentSelection, syncAgentFiles } from '../init/index.js';
27
27
  import { generateReport } from '../report/index.js';
28
28
  import { generateDashboardHTML } from '../dashboard/index.js';
29
29
  import { computeStats, computeSeverity, computeExposures } from '../dashboard/data.js';
30
- import { generateThreatReport, serializeModel, listThreatReports, loadThreatReportsForDashboard, FRAMEWORK_LABELS, FRAMEWORK_PROMPTS, buildUserMessage, buildProjectContext, extractCodeSnippets } from '../analyze/index.js';
30
+ import { generateThreatReport, serializeModel, listThreatReports, loadThreatReportsForDashboard, loadPentestData, FRAMEWORK_LABELS, FRAMEWORK_PROMPTS, buildUserMessage, buildProjectContext, extractCodeSnippets } from '../analyze/index.js';
31
31
  import { diffModels, formatDiff, parseAtRef } from '../diff/index.js';
32
32
  import { generateSarif } from '../analyzer/index.js';
33
+ import { diagnosticIcon } from '../parser/format.js';
33
34
  import { C, severityBadge, severityText, severityTextPad, severityOrder, computeGrade, gradeColored, readCodeContext, trunc, bar, fileLink, fileLinkTrunc, cleanCliArtifacts } from './format.js';
34
35
  import { resolveLLMConfig, saveTuiConfig, loadTuiConfig } from './config.js';
35
- import { AGENTS, parseAgentFlag, launchAgent, launchAgentInline, copyToClipboard, buildAnnotatePrompt } from '../agents/index.js';
36
+ import { AGENTS, parseAgentFlag, parseAnnotationModeFlag, launchAgent, launchAgentInline, copyToClipboard, buildAnnotatePrompt } from '../agents/index.js';
36
37
  import { describeConfigSource } from '../agents/config.js';
37
38
  import { getReviewableExposures, applyReviewAction, summarizeReview } from '../review/index.js';
38
39
  import { loadWorkspaceConfig, linkProject, addToWorkspace, removeFromWorkspace, mergeReports, formatMergeSummary, diffMergedReports } from '../workspace/index.js';
@@ -106,6 +107,7 @@ export function cmdHelp() {
106
107
  ['/workspace', 'Show workspace config and linked repos'],
107
108
  ['/link <repos...>', 'Link repos into a workspace (--add / --remove)'],
108
109
  ['/merge <files...>', 'Merge report JSONs into unified dashboard'],
110
+ ['/feature [name]', 'List all features or show detail for one'],
109
111
  ['', ''],
110
112
  ['/gal', 'GAL annotation language guide'],
111
113
  ['/help', 'This help'],
@@ -134,10 +136,12 @@ export function cmdGal() {
134
136
  console.log(H(' GAL — GuardLink Annotation Language'));
135
137
  console.log(H(' ══════════════════════════════════════════════════════════'));
136
138
  console.log('');
137
- console.log(D(' Annotations live in source code comments. GuardLink parses'));
138
- console.log(D(' them to build a live threat model from your codebase.'));
139
+ console.log(D(' Annotations live in source comments or standalone .gal files.'));
140
+ console.log(D(' GuardLink parses them into a live threat model for your codebase.'));
139
141
  console.log('');
140
142
  console.log(D(' Syntax: @verb subject [preposition object] [-- "description"]'));
143
+ console.log(D(' Inline examples below use comment prefixes; raw .gal files use the same lines without // or #.'));
144
+ console.log(D(' In .gal files, use @source file:<path> line:<n> [symbol:<name>] to anchor following annotations.'));
141
145
  console.log('');
142
146
  // ── DEFINITIONS ──────────────────────────────────────────────────
143
147
  console.log(H(' ── Definitions ─────────────────────────────────────────────'));
@@ -224,6 +228,16 @@ export function cmdGal() {
224
228
  console.log(D(' Document a security assumption about an asset.'));
225
229
  console.log(EX(' // @assumes api.gateway -- "Upstream WAF filters malformed requests"'));
226
230
  console.log('');
231
+ // ── FEATURE TAGGING ──────────────────────────────────────────────
232
+ console.log(H(' ── Feature Tagging ─────────────────────────────────────────'));
233
+ console.log('');
234
+ console.log(` ${V('@feature')} ${K('"Feature Name"')} ${D('[-- "description"]')}`);
235
+ console.log(D(' Tag code with a feature name for filtering reports and dashboards.'));
236
+ console.log(D(' A file can have multiple @feature tags. All annotations in that file'));
237
+ console.log(D(' are associated with the tagged features.'));
238
+ console.log(EX(' // @feature "SSO Login" -- "Single sign-on authentication flow"'));
239
+ console.log(EX(' // @feature "Payment Processing"'));
240
+ console.log('');
227
241
  console.log(` ${V('@comment')} ${D('[-- "description"]')}`);
228
242
  console.log(D(' Free-form developer security note (no structural effect).'));
229
243
  console.log(EX(' // @comment -- "TODO — add rate limiting before v2 launch"'));
@@ -772,7 +786,9 @@ export async function cmdValidate(ctx) {
772
786
  if (allDiags.length > 0) {
773
787
  console.log('');
774
788
  for (const d of allDiags) {
775
- const prefix = d.level === 'error' ? C.error(' ✗') : C.warn(' ⚠');
789
+ const icon = diagnosticIcon(d.level);
790
+ const color = d.level === 'fatal' || d.level === 'error' ? C.error : C.warn;
791
+ const prefix = color(` ${icon}`);
776
792
  const loc = d.file ? `${fileLink(d.file, d.line, ctx.root)}` : '';
777
793
  console.log(`${prefix} ${d.message}${loc ? ` ${C.dim(loc)}` : ''}`);
778
794
  }
@@ -1292,10 +1308,15 @@ export function cmdThreatReports(ctx) {
1292
1308
  }
1293
1309
  // ─── /annotate ───────────────────────────────────────────────────────
1294
1310
  export async function cmdAnnotate(args, ctx) {
1295
- const { agent: flagAgent, cleanArgs } = parseAgentFlag(args);
1311
+ const { mode: annotationMode, cleanArgs: argsWithoutMode, error: modeError } = parseAnnotationModeFlag(args);
1312
+ if (modeError) {
1313
+ console.log(C.warn(` ${modeError}`));
1314
+ return;
1315
+ }
1316
+ const { agent: flagAgent, cleanArgs } = parseAgentFlag(argsWithoutMode);
1296
1317
  if (!cleanArgs.trim()) {
1297
- console.log(C.warn(' Usage: /annotate <prompt> [--claude-code|--codex|--gemini|--cursor|--windsurf|--clipboard]'));
1298
- console.log(C.dim(' Example: /annotate "annotate auth endpoints for OWASP Top 10" --claude-code'));
1318
+ console.log(C.warn(' Usage: /annotate <prompt> [--mode inline|external] [--claude-code|--codex|--gemini|--cursor|--windsurf|--clipboard]'));
1319
+ console.log(C.dim(' Example: /annotate "annotate auth endpoints for OWASP Top 10" --mode external --claude-code'));
1299
1320
  return;
1300
1321
  }
1301
1322
  console.log('');
@@ -1304,7 +1325,7 @@ export async function cmdAnnotate(args, ctx) {
1304
1325
  if (!agent)
1305
1326
  return;
1306
1327
  // Build context prompt using shared builder
1307
- const prompt = buildAnnotatePrompt(cleanArgs.trim(), ctx.root, ctx.model);
1328
+ const prompt = buildAnnotatePrompt(cleanArgs.trim(), ctx.root, ctx.model, annotationMode);
1308
1329
  // For terminal agents: foreground spawn (agent takes over terminal)
1309
1330
  if (agent.cmd) {
1310
1331
  const copied = copyToClipboard(prompt);
@@ -1559,7 +1580,7 @@ export async function cmdReview(args, ctx) {
1559
1580
  console.log(` ${C.success('✓')} Marked for remediation — ${result.linesInserted} line(s) written\n`);
1560
1581
  }
1561
1582
  else {
1562
- results.push({ exposure: reviewable, action: { decision: 'skip', justification: '' }, linesInserted: 0 });
1583
+ results.push({ exposure: reviewable, action: { decision: 'skip', justification: '' }, linesInserted: 0, targetFile: reviewable.exposure.location.file });
1563
1584
  console.log(` ${C.dim('— Skipped')}\n`);
1564
1585
  }
1565
1586
  }
@@ -1602,7 +1623,8 @@ export async function cmdDashboard(ctx) {
1602
1623
  return;
1603
1624
  }
1604
1625
  const analyses = loadThreatReportsForDashboard(ctx.root);
1605
- const html = generateDashboardHTML(ctx.model, ctx.root, analyses);
1626
+ const pentestData = loadPentestData(ctx.root);
1627
+ const html = generateDashboardHTML(ctx.model, ctx.root, analyses, pentestData);
1606
1628
  const outFile = resolve(ctx.root, 'threat-dashboard.html');
1607
1629
  const { writeFile } = await import('node:fs/promises');
1608
1630
  await writeFile(outFile, html);
@@ -1643,6 +1665,70 @@ export function cmdWorkspace(ctx) {
1643
1665
  console.log(C.dim(' /merge to combine reports · /link --add to add a repo · /link --remove to remove'));
1644
1666
  console.log('');
1645
1667
  }
1668
+ // ─── /feature ────────────────────────────────────────────────────────
1669
+ export function cmdFeature(args, ctx) {
1670
+ if (!ctx.model) {
1671
+ console.log(C.warn(' No threat model. Run /parse first.'));
1672
+ return;
1673
+ }
1674
+ const features = listFeatures(ctx.model);
1675
+ if (features.length === 0) {
1676
+ console.log(' No @feature annotations found.');
1677
+ console.log(C.dim(' Tag code with: // @feature "Feature Name" -- "description"'));
1678
+ console.log('');
1679
+ return;
1680
+ }
1681
+ const name = args.trim();
1682
+ if (name) {
1683
+ // Show detail for specific feature
1684
+ const filtered = filterByFeature(ctx.model, [name]);
1685
+ const total = filtered.assets.length + filtered.threats.length +
1686
+ filtered.controls.length + filtered.mitigations.length + filtered.exposures.length +
1687
+ filtered.confirmed.length + filtered.flows.length + filtered.boundaries.length;
1688
+ if (total === 0) {
1689
+ console.log(C.warn(` No annotations found for feature "${name}".`));
1690
+ console.log(` Available: ${features.map(f => `"${f}"`).join(', ')}`);
1691
+ console.log('');
1692
+ return;
1693
+ }
1694
+ console.log(` Feature: ${C.bold(`"${name}"`)}`);
1695
+ console.log('');
1696
+ console.log(` Assets: ${filtered.assets.length}`);
1697
+ console.log(` Threats: ${filtered.threats.length}`);
1698
+ console.log(` Controls: ${filtered.controls.length}`);
1699
+ console.log(` Mitigations: ${filtered.mitigations.length}`);
1700
+ console.log(` Exposures: ${filtered.exposures.length}`);
1701
+ if (filtered.confirmed.length > 0)
1702
+ console.log(` Confirmed: ${filtered.confirmed.length} 🔴`);
1703
+ console.log(` Flows: ${filtered.flows.length}`);
1704
+ console.log(` Boundaries: ${filtered.boundaries.length}`);
1705
+ if (filtered.exposures.length > 0) {
1706
+ console.log('');
1707
+ console.log(` ${C.bold('Exposures:')}`);
1708
+ for (const e of filtered.exposures) {
1709
+ console.log(` ${e.asset} → ${e.threat} ${severityBadge(e.severity || 'unset')} (${fileLink(e.location.file, e.location.line)})`);
1710
+ }
1711
+ }
1712
+ console.log('');
1713
+ return;
1714
+ }
1715
+ // List all features
1716
+ const summaries = getFeatureSummaries(ctx.model);
1717
+ console.log(` ${C.bold('Features:')}`);
1718
+ console.log('');
1719
+ for (const s of summaries) {
1720
+ const files = s.files.length === 1 ? '1 file' : `${s.files.length} files`;
1721
+ const exposureInfo = s.exposures > 0 ? C.red(` | ${s.exposures} exposure(s)`) : '';
1722
+ const confirmedInfo = s.confirmed > 0 ? C.red(` | ${s.confirmed} confirmed`) : '';
1723
+ console.log(` ${C.cyan(`"${s.name}"`)} (${files}, ${s.annotations} annotations${exposureInfo}${confirmedInfo})`);
1724
+ for (const f of s.files) {
1725
+ console.log(` → ${C.dim(f)}`);
1726
+ }
1727
+ }
1728
+ console.log('');
1729
+ console.log(C.dim(` ${features.length} feature(s) total. Use /feature <name> for detail.`));
1730
+ console.log('');
1731
+ }
1646
1732
  // ─── /link ───────────────────────────────────────────────────────────
1647
1733
  export async function cmdLink(args, ctx) {
1648
1734
  const parts = args.trim().split(/\s+/).filter(Boolean);