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.
- package/CHANGELOG.md +111 -7
- package/README.md +53 -5
- package/dist/agents/config.d.ts +7 -0
- package/dist/agents/config.d.ts.map +1 -1
- package/dist/agents/config.js.map +1 -1
- package/dist/agents/index.d.ts +9 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +36 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/launcher.d.ts.map +1 -1
- package/dist/agents/launcher.js +5 -0
- package/dist/agents/launcher.js.map +1 -1
- package/dist/agents/prompts.d.ts +16 -1
- package/dist/agents/prompts.d.ts.map +1 -1
- package/dist/agents/prompts.js +511 -16
- package/dist/agents/prompts.js.map +1 -1
- package/dist/analyze/format.d.ts +72 -0
- package/dist/analyze/format.d.ts.map +1 -0
- package/dist/analyze/format.js +176 -0
- package/dist/analyze/format.js.map +1 -0
- package/dist/analyze/index.d.ts +76 -0
- package/dist/analyze/index.d.ts.map +1 -1
- package/dist/analyze/index.js +165 -2
- package/dist/analyze/index.js.map +1 -1
- package/dist/analyze/prompts.d.ts +3 -2
- package/dist/analyze/prompts.d.ts.map +1 -1
- package/dist/analyze/prompts.js +17 -3
- package/dist/analyze/prompts.js.map +1 -1
- package/dist/analyzer/sarif.d.ts +3 -2
- package/dist/analyzer/sarif.d.ts.map +1 -1
- package/dist/analyzer/sarif.js +29 -3
- package/dist/analyzer/sarif.js.map +1 -1
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +408 -37
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/data.d.ts +11 -0
- package/dist/dashboard/data.d.ts.map +1 -1
- package/dist/dashboard/data.js +12 -0
- package/dist/dashboard/data.js.map +1 -1
- package/dist/dashboard/diagrams.d.ts +81 -12
- package/dist/dashboard/diagrams.d.ts.map +1 -1
- package/dist/dashboard/diagrams.js +750 -362
- package/dist/dashboard/diagrams.js.map +1 -1
- package/dist/dashboard/generate.d.ts +5 -2
- package/dist/dashboard/generate.d.ts.map +1 -1
- package/dist/dashboard/generate.js +2516 -244
- package/dist/dashboard/generate.js.map +1 -1
- package/dist/diff/engine.d.ts +2 -1
- package/dist/diff/engine.d.ts.map +1 -1
- package/dist/diff/engine.js +3 -2
- package/dist/diff/engine.js.map +1 -1
- package/dist/diff/git.js +3 -3
- package/dist/diff/git.js.map +1 -1
- package/dist/init/index.d.ts +7 -0
- package/dist/init/index.d.ts.map +1 -1
- package/dist/init/index.js +82 -27
- package/dist/init/index.js.map +1 -1
- package/dist/init/migrate.d.ts +39 -0
- package/dist/init/migrate.d.ts.map +1 -0
- package/dist/init/migrate.js +45 -0
- package/dist/init/migrate.js.map +1 -0
- package/dist/init/templates.d.ts +8 -0
- package/dist/init/templates.d.ts.map +1 -1
- package/dist/init/templates.js +68 -6
- package/dist/init/templates.js.map +1 -1
- package/dist/mcp/lookup.d.ts +1 -0
- package/dist/mcp/lookup.d.ts.map +1 -1
- package/dist/mcp/lookup.js +138 -10
- package/dist/mcp/lookup.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +32 -15
- package/dist/mcp/server.js.map +1 -1
- package/dist/parser/clear.d.ts +2 -1
- package/dist/parser/clear.d.ts.map +1 -1
- package/dist/parser/clear.js +19 -29
- package/dist/parser/clear.js.map +1 -1
- package/dist/parser/comment-strip.d.ts +5 -0
- package/dist/parser/comment-strip.d.ts.map +1 -1
- package/dist/parser/comment-strip.js +8 -0
- package/dist/parser/comment-strip.js.map +1 -1
- package/dist/parser/feature-filter.d.ts +42 -0
- package/dist/parser/feature-filter.d.ts.map +1 -0
- package/dist/parser/feature-filter.js +109 -0
- package/dist/parser/feature-filter.js.map +1 -0
- package/dist/parser/format.d.ts +24 -0
- package/dist/parser/format.d.ts.map +1 -0
- package/dist/parser/format.js +29 -0
- package/dist/parser/format.js.map +1 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +1 -0
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/parse-file.d.ts +1 -0
- package/dist/parser/parse-file.d.ts.map +1 -1
- package/dist/parser/parse-file.js +34 -9
- package/dist/parser/parse-file.js.map +1 -1
- package/dist/parser/parse-line.d.ts +9 -0
- package/dist/parser/parse-line.d.ts.map +1 -1
- package/dist/parser/parse-line.js +100 -26
- package/dist/parser/parse-line.js.map +1 -1
- package/dist/parser/parse-project.d.ts +1 -0
- package/dist/parser/parse-project.d.ts.map +1 -1
- package/dist/parser/parse-project.js +36 -2
- package/dist/parser/parse-project.js.map +1 -1
- package/dist/parser/validate.d.ts +3 -0
- package/dist/parser/validate.d.ts.map +1 -1
- package/dist/parser/validate.js +7 -0
- package/dist/parser/validate.js.map +1 -1
- package/dist/report/index.d.ts +1 -0
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +1 -0
- package/dist/report/index.js.map +1 -1
- package/dist/report/report.d.ts.map +1 -1
- package/dist/report/report.js +924 -24
- package/dist/report/report.js.map +1 -1
- package/dist/report/sequence.d.ts +11 -0
- package/dist/report/sequence.d.ts.map +1 -0
- package/dist/report/sequence.js +140 -0
- package/dist/report/sequence.js.map +1 -0
- package/dist/review/index.d.ts +3 -1
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +77 -35
- package/dist/review/index.js.map +1 -1
- package/dist/tui/commands.d.ts +1 -0
- package/dist/tui/commands.d.ts.map +1 -1
- package/dist/tui/commands.js +98 -12
- package/dist/tui/commands.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +7 -2
- package/dist/tui/index.js.map +1 -1
- package/dist/types/index.d.ts +59 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/workspace/merge.d.ts.map +1 -1
- package/dist/workspace/merge.js +6 -2
- package/dist/workspace/merge.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GuardLink — Feature-based filtering.
|
|
3
|
+
*
|
|
4
|
+
* Filters a ThreatModel to only include annotations that belong to
|
|
5
|
+
* specific features. Feature association is determined by file-level
|
|
6
|
+
* proximity: if a file contains @feature "X", all annotations in
|
|
7
|
+
* that file are considered part of feature "X".
|
|
8
|
+
*
|
|
9
|
+
* @comment -- "Pure filtering utility; no I/O"
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Unique feature names found in the model, sorted alphabetically.
|
|
13
|
+
*/
|
|
14
|
+
export function listFeatures(model) {
|
|
15
|
+
const names = new Set();
|
|
16
|
+
for (const f of model.features) {
|
|
17
|
+
names.add(f.feature);
|
|
18
|
+
}
|
|
19
|
+
return [...names].sort();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build a map of file → Set<feature name> from feature annotations.
|
|
23
|
+
*/
|
|
24
|
+
function buildFileFeatureMap(model) {
|
|
25
|
+
const map = new Map();
|
|
26
|
+
for (const f of model.features) {
|
|
27
|
+
const file = f.location.file;
|
|
28
|
+
if (!map.has(file))
|
|
29
|
+
map.set(file, new Set());
|
|
30
|
+
map.get(file).add(f.feature);
|
|
31
|
+
}
|
|
32
|
+
return map;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Filter a ThreatModel to only annotations in files tagged with
|
|
36
|
+
* one or more of the given feature names.
|
|
37
|
+
*
|
|
38
|
+
* Returns a new ThreatModel with only matching annotations.
|
|
39
|
+
* Feature matching is case-insensitive.
|
|
40
|
+
*/
|
|
41
|
+
export function filterByFeature(model, featureNames) {
|
|
42
|
+
const wantedLower = new Set(featureNames.map(n => n.toLowerCase()));
|
|
43
|
+
const fileFeatureMap = buildFileFeatureMap(model);
|
|
44
|
+
// Determine which files match any of the requested features
|
|
45
|
+
const matchingFiles = new Set();
|
|
46
|
+
for (const [file, features] of fileFeatureMap) {
|
|
47
|
+
for (const f of features) {
|
|
48
|
+
if (wantedLower.has(f.toLowerCase())) {
|
|
49
|
+
matchingFiles.add(file);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Filter helper
|
|
55
|
+
const inFeature = (arr) => arr.filter(item => matchingFiles.has(item.location.file));
|
|
56
|
+
return {
|
|
57
|
+
...model,
|
|
58
|
+
// Preserve metadata
|
|
59
|
+
annotations_parsed: model.annotations_parsed,
|
|
60
|
+
source_files: model.source_files,
|
|
61
|
+
annotated_files: model.annotated_files.filter(f => matchingFiles.has(f)),
|
|
62
|
+
unannotated_files: model.unannotated_files,
|
|
63
|
+
// Filter each category
|
|
64
|
+
assets: inFeature(model.assets),
|
|
65
|
+
threats: inFeature(model.threats),
|
|
66
|
+
controls: inFeature(model.controls),
|
|
67
|
+
mitigations: inFeature(model.mitigations),
|
|
68
|
+
exposures: inFeature(model.exposures),
|
|
69
|
+
confirmed: inFeature(model.confirmed),
|
|
70
|
+
acceptances: inFeature(model.acceptances),
|
|
71
|
+
transfers: inFeature(model.transfers),
|
|
72
|
+
flows: inFeature(model.flows),
|
|
73
|
+
boundaries: inFeature(model.boundaries),
|
|
74
|
+
validations: inFeature(model.validations),
|
|
75
|
+
audits: inFeature(model.audits),
|
|
76
|
+
ownership: inFeature(model.ownership),
|
|
77
|
+
data_handling: inFeature(model.data_handling),
|
|
78
|
+
assumptions: inFeature(model.assumptions),
|
|
79
|
+
shields: inFeature(model.shields),
|
|
80
|
+
features: inFeature(model.features),
|
|
81
|
+
comments: inFeature(model.comments),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get summary stats for each feature in the model.
|
|
86
|
+
*/
|
|
87
|
+
export function getFeatureSummaries(model) {
|
|
88
|
+
const featureNames = listFeatures(model);
|
|
89
|
+
return featureNames.map(name => {
|
|
90
|
+
const filtered = filterByFeature(model, [name]);
|
|
91
|
+
return {
|
|
92
|
+
name,
|
|
93
|
+
files: [...new Set(model.features.filter(f => f.feature.toLowerCase() === name.toLowerCase()).map(f => f.location.file))],
|
|
94
|
+
annotations: filtered.assets.length + filtered.threats.length + filtered.controls.length +
|
|
95
|
+
filtered.mitigations.length + filtered.exposures.length + filtered.confirmed.length +
|
|
96
|
+
filtered.acceptances.length + filtered.transfers.length + filtered.flows.length +
|
|
97
|
+
filtered.boundaries.length + filtered.validations.length + filtered.audits.length +
|
|
98
|
+
filtered.ownership.length + filtered.data_handling.length + filtered.assumptions.length +
|
|
99
|
+
filtered.features.length + filtered.comments.length + filtered.shields.length,
|
|
100
|
+
exposures: filtered.exposures.length,
|
|
101
|
+
mitigations: filtered.mitigations.length,
|
|
102
|
+
assets: filtered.assets.length,
|
|
103
|
+
threats: filtered.threats.length,
|
|
104
|
+
flows: filtered.flows.length,
|
|
105
|
+
confirmed: filtered.confirmed.length,
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=feature-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-filter.js","sourceRoot":"","sources":["../../src/parser/feature-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GuardLink parser — Diagnostic formatting helpers.
|
|
3
|
+
*
|
|
4
|
+
* Tiny pure helpers shared by the CLI's `printDiagnostics` and the TUI's
|
|
5
|
+
* status-command printer so the level-to-icon mapping lives in one place.
|
|
6
|
+
* If a future iteration adds a new diagnostic tier, only this file needs
|
|
7
|
+
* to change for the visual treatment to stay consistent across surfaces.
|
|
8
|
+
*/
|
|
9
|
+
import type { ParseDiagnostic } from '../types/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Returns the icon character for a diagnostic level. No color, no padding,
|
|
12
|
+
* no extra whitespace — callers that want those (e.g. the TUI's
|
|
13
|
+
* `C.error(' ' + diagnosticIcon(...))`) wrap the return value themselves.
|
|
14
|
+
*
|
|
15
|
+
* - `'warning'` → `⚠` (informational, never blocks)
|
|
16
|
+
* - `'error'` → `✗` (annotation skipped, model continues)
|
|
17
|
+
* - `'fatal'` → `✗✗` (model is unsafe to render; consumer must abort)
|
|
18
|
+
*
|
|
19
|
+
* The switch is exhaustive over `ParseDiagnostic['level']` — TypeScript
|
|
20
|
+
* will flag this function if a new level is added to the union without a
|
|
21
|
+
* matching case here.
|
|
22
|
+
*/
|
|
23
|
+
export declare function diagnosticIcon(level: ParseDiagnostic['level']): string;
|
|
24
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/parser/format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,MAAM,CAMtE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GuardLink parser — Diagnostic formatting helpers.
|
|
3
|
+
*
|
|
4
|
+
* Tiny pure helpers shared by the CLI's `printDiagnostics` and the TUI's
|
|
5
|
+
* status-command printer so the level-to-icon mapping lives in one place.
|
|
6
|
+
* If a future iteration adds a new diagnostic tier, only this file needs
|
|
7
|
+
* to change for the visual treatment to stay consistent across surfaces.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Returns the icon character for a diagnostic level. No color, no padding,
|
|
11
|
+
* no extra whitespace — callers that want those (e.g. the TUI's
|
|
12
|
+
* `C.error(' ' + diagnosticIcon(...))`) wrap the return value themselves.
|
|
13
|
+
*
|
|
14
|
+
* - `'warning'` → `⚠` (informational, never blocks)
|
|
15
|
+
* - `'error'` → `✗` (annotation skipped, model continues)
|
|
16
|
+
* - `'fatal'` → `✗✗` (model is unsafe to render; consumer must abort)
|
|
17
|
+
*
|
|
18
|
+
* The switch is exhaustive over `ParseDiagnostic['level']` — TypeScript
|
|
19
|
+
* will flag this function if a new level is added to the union without a
|
|
20
|
+
* matching case here.
|
|
21
|
+
*/
|
|
22
|
+
export function diagnosticIcon(level) {
|
|
23
|
+
switch (level) {
|
|
24
|
+
case 'fatal': return '✗✗';
|
|
25
|
+
case 'error': return '✗';
|
|
26
|
+
case 'warning': return '⚠';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/parser/format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,KAA+B;IAC5D,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,OAAO,CAAC,CAAG,OAAO,IAAI,CAAC;QAC5B,KAAK,OAAO,CAAC,CAAG,OAAO,GAAG,CAAC;QAC3B,KAAK,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC;IAC7B,CAAC;AACH,CAAC"}
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -10,4 +10,6 @@ export { stripCommentPrefix, commentStyleForExt } from './comment-strip.js';
|
|
|
10
10
|
export { findDanglingRefs, findUnmitigatedExposures, findAcceptedWithoutAudit, findAcceptedExposures } from './validate.js';
|
|
11
11
|
export { clearAnnotations } from './clear.js';
|
|
12
12
|
export type { ClearAnnotationsOptions, ClearAnnotationsResult } from './clear.js';
|
|
13
|
+
export { listFeatures, filterByFeature, getFeatureSummaries } from './feature-filter.js';
|
|
14
|
+
export type { FeatureSummary } from './feature-filter.js';
|
|
13
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC5H,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC5H,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACzF,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/parser/index.js
CHANGED
|
@@ -8,4 +8,5 @@ export { normalizeName, resolveSeverity, unescapeDescription } from './normalize
|
|
|
8
8
|
export { stripCommentPrefix, commentStyleForExt } from './comment-strip.js';
|
|
9
9
|
export { findDanglingRefs, findUnmitigatedExposures, findAcceptedWithoutAudit, findAcceptedExposures } from './validate.js';
|
|
10
10
|
export { clearAnnotations } from './clear.js';
|
|
11
|
+
export { listFeatures, filterByFeature, getFeatureSummaries } from './feature-filter.js';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
package/dist/parser/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC5H,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC5H,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GuardLink — File-level parser.
|
|
3
3
|
* Reads source files and extracts all GuardLink annotations.
|
|
4
|
+
* Standalone .gal files are treated as raw annotation text.
|
|
4
5
|
*
|
|
5
6
|
* @exposes #parser to #path-traversal [high] cwe:CWE-22 -- "File path from caller read via readFile; no validation here"
|
|
6
7
|
* @exposes #parser to #dos [medium] cwe:CWE-400 -- "Large files loaded entirely into memory"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-file.d.ts","sourceRoot":"","sources":["../../src/parser/parse-file.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"parse-file.d.ts","sourceRoot":"","sources":["../../src/parser/parse-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAA+B,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAKlG;;GAEG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAGtE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAkB,GAAG,WAAW,CA+FtF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GuardLink — File-level parser.
|
|
3
3
|
* Reads source files and extracts all GuardLink annotations.
|
|
4
|
+
* Standalone .gal files are treated as raw annotation text.
|
|
4
5
|
*
|
|
5
6
|
* @exposes #parser to #path-traversal [high] cwe:CWE-22 -- "File path from caller read via readFile; no validation here"
|
|
6
7
|
* @exposes #parser to #dos [medium] cwe:CWE-400 -- "Large files loaded entirely into memory"
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
* @flows #parser -> Annotations via parseString -- "Parsed annotation output"
|
|
10
11
|
*/
|
|
11
12
|
import { readFile } from 'node:fs/promises';
|
|
12
|
-
import { stripCommentPrefix } from './comment-strip.js';
|
|
13
|
+
import { isStandaloneAnnotationFile, stripCommentPrefix } from './comment-strip.js';
|
|
13
14
|
import { parseLine } from './parse-line.js';
|
|
14
15
|
import { unescapeDescription } from './normalize.js';
|
|
15
16
|
/**
|
|
@@ -29,20 +30,24 @@ export function parseString(content, filePath = '<input>') {
|
|
|
29
30
|
const diagnostics = [];
|
|
30
31
|
let lastAnnotation = null;
|
|
31
32
|
let inShield = false;
|
|
33
|
+
const allowRawAnnotationLines = isStandaloneAnnotationFile(filePath);
|
|
34
|
+
let currentSource = null;
|
|
32
35
|
for (let i = 0; i < lines.length; i++) {
|
|
33
36
|
const lineNum = i + 1; // 1-indexed
|
|
34
37
|
const rawLine = lines[i];
|
|
35
|
-
// Strip comment prefix
|
|
36
|
-
|
|
38
|
+
// Strip comment prefix unless this is a standalone .gal file, where
|
|
39
|
+
// annotations are stored as raw lines instead of host-language comments.
|
|
40
|
+
const inner = allowRawAnnotationLines ? rawLine : stripCommentPrefix(rawLine);
|
|
37
41
|
if (inner === null) {
|
|
38
42
|
lastAnnotation = null;
|
|
39
43
|
continue;
|
|
40
44
|
}
|
|
45
|
+
const text = inner.trimStart();
|
|
41
46
|
// Check for shield block boundaries — always parse these even inside shields
|
|
42
|
-
const trimmed =
|
|
47
|
+
const trimmed = text.trim();
|
|
43
48
|
if (trimmed.startsWith('@shield:end')) {
|
|
44
49
|
const location = { file: filePath, line: lineNum };
|
|
45
|
-
const result = parseLine(
|
|
50
|
+
const result = parseLine(text, location);
|
|
46
51
|
if (result.annotation)
|
|
47
52
|
annotations.push(result.annotation);
|
|
48
53
|
inShield = false;
|
|
@@ -51,7 +56,7 @@ export function parseString(content, filePath = '<input>') {
|
|
|
51
56
|
}
|
|
52
57
|
if (trimmed.startsWith('@shield:begin')) {
|
|
53
58
|
const location = { file: filePath, line: lineNum };
|
|
54
|
-
const result = parseLine(
|
|
59
|
+
const result = parseLine(text, location);
|
|
55
60
|
if (result.annotation)
|
|
56
61
|
annotations.push(result.annotation);
|
|
57
62
|
inShield = true;
|
|
@@ -62,7 +67,7 @@ export function parseString(content, filePath = '<input>') {
|
|
|
62
67
|
if (inShield)
|
|
63
68
|
continue;
|
|
64
69
|
// Check for continuation line: -- "..."
|
|
65
|
-
const contMatch =
|
|
70
|
+
const contMatch = text.match(/^--\s*"((?:[^"\\]|\\.)*)"/);
|
|
66
71
|
if (contMatch && lastAnnotation) {
|
|
67
72
|
// Append to last annotation's description
|
|
68
73
|
const contDesc = unescapeDescription(contMatch[1]);
|
|
@@ -76,10 +81,30 @@ export function parseString(content, filePath = '<input>') {
|
|
|
76
81
|
}
|
|
77
82
|
// Try to parse as annotation
|
|
78
83
|
const location = { file: filePath, line: lineNum };
|
|
79
|
-
const result = parseLine(
|
|
84
|
+
const result = parseLine(text, location);
|
|
85
|
+
if (result.sourceDirective) {
|
|
86
|
+
currentSource = {
|
|
87
|
+
file: result.sourceDirective.file,
|
|
88
|
+
line: result.sourceDirective.line,
|
|
89
|
+
parent_symbol: result.sourceDirective.symbol ?? null,
|
|
90
|
+
};
|
|
91
|
+
lastAnnotation = null;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
80
94
|
if (result.annotation) {
|
|
95
|
+
if (allowRawAnnotationLines && currentSource) {
|
|
96
|
+
result.annotation.location = {
|
|
97
|
+
file: currentSource.file,
|
|
98
|
+
line: currentSource.line,
|
|
99
|
+
parent_symbol: currentSource.parent_symbol ?? null,
|
|
100
|
+
origin_file: filePath,
|
|
101
|
+
origin_line: lineNum,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
81
104
|
annotations.push(result.annotation);
|
|
82
|
-
|
|
105
|
+
if (result.extraAnnotations)
|
|
106
|
+
annotations.push(...result.extraAnnotations);
|
|
107
|
+
lastAnnotation = annotations[annotations.length - 1];
|
|
83
108
|
}
|
|
84
109
|
else {
|
|
85
110
|
if (result.diagnostic) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-file.js","sourceRoot":"","sources":["../../src/parser/parse-file.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"parse-file.js","sourceRoot":"","sources":["../../src/parser/parse-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,10 +7,19 @@
|
|
|
7
7
|
* @comment -- "Regex patterns designed with bounded quantifiers and explicit structure"
|
|
8
8
|
*/
|
|
9
9
|
import type { Annotation, ParseDiagnostic, SourceLocation } from '../types/index.js';
|
|
10
|
+
export interface SourceDirective {
|
|
11
|
+
file: string;
|
|
12
|
+
line: number;
|
|
13
|
+
symbol?: string;
|
|
14
|
+
}
|
|
10
15
|
export interface ParseLineResult {
|
|
11
16
|
annotation: Annotation | null;
|
|
17
|
+
/** Additional annotations from the same line. Used by multi-hop @flows
|
|
18
|
+
* chains (`A -> B -> C`) to emit one pairwise flow per arrow. */
|
|
19
|
+
extraAnnotations?: Annotation[];
|
|
12
20
|
diagnostic: ParseDiagnostic | null;
|
|
13
21
|
isContinuation: boolean;
|
|
22
|
+
sourceDirective?: SourceDirective | null;
|
|
14
23
|
}
|
|
15
24
|
/**
|
|
16
25
|
* Parse a single annotation line (after comment prefix has been stripped).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-line.d.ts","sourceRoot":"","sources":["../../src/parser/parse-line.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EAAE,cAAc,EAChC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"parse-line.d.ts","sourceRoot":"","sources":["../../src/parser/parse-line.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EAAE,cAAc,EAChC,MAAM,mBAAmB,CAAC;AA0F3B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B;sEACkE;IAClE,gBAAgB,CAAC,EAAE,UAAU,EAAE,CAAC;IAChC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,GACvB,eAAe,CA0NjB"}
|
|
@@ -9,14 +9,20 @@
|
|
|
9
9
|
import { normalizeName, resolveSeverity, unescapeDescription } from './normalize.js';
|
|
10
10
|
// ─── Shared regex fragments ──────────────────────────────────────────
|
|
11
11
|
const COMPONENT = String.raw `[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*`;
|
|
12
|
-
|
|
12
|
+
// Quoted ref: any non-newline content between double quotes, with `\"` and
|
|
13
|
+
// `\\` escape support. Mirrors the DESC fragment's character class.
|
|
14
|
+
const QUOTED_REF = String.raw `"(?:[^"\\\n]|\\.)*"`;
|
|
15
|
+
const ASSET_REF = String.raw `(?:#[a-zA-Z0-9_-]+|${QUOTED_REF}|[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)`; // #id, "quoted", or Dotted.Path
|
|
13
16
|
const NAME = String.raw `[A-Za-z]\w*(?:[_\- ][A-Za-z]\w*)*`;
|
|
14
17
|
const ID_DEF = String.raw `\(#([a-zA-Z0-9_-]+)\)`;
|
|
15
18
|
const ID_REF = String.raw `#([a-zA-Z0-9_-]+)`;
|
|
16
|
-
const THREAT_REF = String.raw `(?:#[a-zA-Z0-9_-]
|
|
19
|
+
const THREAT_REF = String.raw `(?:#[a-zA-Z0-9_-]+|${QUOTED_REF}|[A-Za-z]\w*(?:[_\- ][A-Za-z]\w*)*)`;
|
|
17
20
|
const SEVERITY = String.raw `\[(P[0-3]|critical|high|medium|low)\]`;
|
|
18
21
|
const EXT_REF = String.raw `([a-zA-Z]+:[A-Za-z0-9_:.\-]+)`;
|
|
19
22
|
const DESC = String.raw `--\s*"((?:[^"\\]|\\.)*)"`;
|
|
23
|
+
const SOURCE_FILE = String.raw `\S+`;
|
|
24
|
+
const SOURCE_LINE = String.raw `[1-9]\d*`;
|
|
25
|
+
const SOURCE_SYMBOL = String.raw `\S+`;
|
|
20
26
|
// Capture external refs (0 or more, space-separated)
|
|
21
27
|
const EXT_REFS_OPT = String.raw `((?:\s+[a-zA-Z]+:[A-Za-z0-9_:.\-]+)*)`;
|
|
22
28
|
// ─── Verb-specific patterns ──────────────────────────────────────────
|
|
@@ -29,10 +35,11 @@ const PATTERNS = {
|
|
|
29
35
|
mitigates: new RegExp(String.raw `^@mitigates\s+(${ASSET_REF})\s+against\s+(${THREAT_REF})(?:\s+using\s+(${THREAT_REF}))?(?:\s+${DESC})?$`),
|
|
30
36
|
mitigates_v1: new RegExp(String.raw `^@mitigates\s+(${ASSET_REF})\s+against\s+(${THREAT_REF})(?:\s+with\s+(${THREAT_REF}))?(?:\s+${DESC})?$`),
|
|
31
37
|
exposes: new RegExp(String.raw `^@exposes\s+(${ASSET_REF})\s+to\s+(${THREAT_REF})(?:\s+${SEVERITY})?${EXT_REFS_OPT}(?:\s+${DESC})?$`),
|
|
38
|
+
confirmed: new RegExp(String.raw `^@confirmed\s+(${THREAT_REF})\s+on\s+(${ASSET_REF})(?:\s+${SEVERITY})?${EXT_REFS_OPT}(?:\s+${DESC})?$`),
|
|
32
39
|
accepts: new RegExp(String.raw `^@accepts\s+(${THREAT_REF})\s+on\s+(${ASSET_REF})(?:\s+${DESC})?$`),
|
|
33
40
|
accepts_v1: new RegExp(String.raw `^@accepts\s+(${THREAT_REF})\s+to\s+(${ASSET_REF})(?:\s+${DESC})?$`),
|
|
34
41
|
transfers: new RegExp(String.raw `^@transfers\s+(${THREAT_REF})\s+from\s+(${ASSET_REF})\s+to\s+(${ASSET_REF})(?:\s+${DESC})?$`),
|
|
35
|
-
flows: new RegExp(String.raw `^@flows\s+(${ASSET_REF}
|
|
42
|
+
flows: new RegExp(String.raw `^@flows\s+(${ASSET_REF}(?:\s+->\s+${ASSET_REF})+)(?:\s+via\s+((?:(?!\s+--\s*").)+?))?(?:\s+${DESC})?$`),
|
|
36
43
|
boundary: new RegExp(String.raw `^@boundary\s+(?:between\s+)?(${ASSET_REF})\s+and\s+(${ASSET_REF})(?:\s+${ID_DEF})?(?:\s+${DESC})?$`),
|
|
37
44
|
boundary_pipe: new RegExp(String.raw `^@boundary\s+(${ASSET_REF})\s*\|\s*(${ASSET_REF})(?:\s+${ID_DEF})?(?:\s+${DESC})?$`),
|
|
38
45
|
connects_v1: new RegExp(String.raw `^@connects\s+(${ASSET_REF})\s+to\s+(${ASSET_REF})(?:\s+${DESC})?$`),
|
|
@@ -43,8 +50,12 @@ const PATTERNS = {
|
|
|
43
50
|
owns: new RegExp(String.raw `^@owns\s+([a-zA-Z0-9_-]+)\s+for\s+(${ASSET_REF})(?:\s+${DESC})?$`),
|
|
44
51
|
handles: new RegExp(String.raw `^@handles\s+(pii|phi|financial|secrets|internal|public)\s+on\s+(${ASSET_REF})(?:\s+${DESC})?$`, 'i'),
|
|
45
52
|
assumes: new RegExp(String.raw `^@assumes\s+(${ASSET_REF})(?:\s+${DESC})?$`),
|
|
53
|
+
// Metadata — feature tagging
|
|
54
|
+
feature: new RegExp(String.raw `^@feature\s+"((?:[^"\\]|\\.)*)"(?:\s+${DESC})?$`),
|
|
46
55
|
// Comment — developer note, description only
|
|
47
56
|
comment: new RegExp(String.raw `^@comment(?:\s+${DESC})?$`),
|
|
57
|
+
// Standalone .gal directive — sets logical source location for following annotations
|
|
58
|
+
source: new RegExp(String.raw `^@source\s+file:(${SOURCE_FILE})\s+line:(${SOURCE_LINE})(?:\s+symbol:(${SOURCE_SYMBOL}))?$`),
|
|
48
59
|
// Special
|
|
49
60
|
shield: new RegExp(String.raw `^@shield(?!:)(?:\s+${DESC})?$`),
|
|
50
61
|
shield_begin: new RegExp(String.raw `^@shield:begin(?:\s+${DESC})?$`),
|
|
@@ -56,8 +67,15 @@ function extractExternalRefs(raw) {
|
|
|
56
67
|
return [];
|
|
57
68
|
return raw.trim().split(/\s+/).filter(r => /^[a-zA-Z]+:[A-Za-z0-9_:.\-]+$/.test(r));
|
|
58
69
|
}
|
|
59
|
-
// ─── Ref resolver: #id or
|
|
70
|
+
// ─── Ref resolver: #id, "quoted", or Dotted.Path → canonical string ───
|
|
71
|
+
/** Normalize a captured ASSET_REF or THREAT_REF for storage in the model.
|
|
72
|
+
* Strips surrounding double quotes and processes escape sequences (\", \\)
|
|
73
|
+
* when the user wrote a quoted ref like `"User Browser"` or `"/api/login"`.
|
|
74
|
+
* Pass-through for `#id` and `Dotted.Path` forms. */
|
|
60
75
|
function resolveRef(ref) {
|
|
76
|
+
if (ref.length >= 2 && ref.charCodeAt(0) === 0x22 /* " */ && ref.charCodeAt(ref.length - 1) === 0x22) {
|
|
77
|
+
return unescapeDescription(ref.slice(1, -1));
|
|
78
|
+
}
|
|
61
79
|
return ref;
|
|
62
80
|
}
|
|
63
81
|
/**
|
|
@@ -72,9 +90,9 @@ export function parseLine(text, location) {
|
|
|
72
90
|
// Check for continuation line (-- "...")
|
|
73
91
|
const contMatch = trimmed.match(new RegExp(String.raw `^${DESC}$`));
|
|
74
92
|
if (contMatch) {
|
|
75
|
-
return { annotation: null, diagnostic: null, isContinuation: true };
|
|
93
|
+
return { annotation: null, diagnostic: null, isContinuation: true, sourceDirective: null };
|
|
76
94
|
}
|
|
77
|
-
return { annotation: null, diagnostic: null, isContinuation: false };
|
|
95
|
+
return { annotation: null, diagnostic: null, isContinuation: false, sourceDirective: null };
|
|
78
96
|
}
|
|
79
97
|
const base = { location, raw: trimmed };
|
|
80
98
|
let m;
|
|
@@ -102,7 +120,7 @@ export function parseLine(text, location) {
|
|
|
102
120
|
// ── @mitigates ──
|
|
103
121
|
if ((m = trimmed.match(PATTERNS.mitigates)) || (m = trimmed.match(PATTERNS.mitigates_v1))) {
|
|
104
122
|
return ok({
|
|
105
|
-
...base, verb: 'mitigates', asset: m[1],
|
|
123
|
+
...base, verb: 'mitigates', asset: resolveRef(m[1]),
|
|
106
124
|
threat: resolveRef(m[2]), control: m[3] ? resolveRef(m[3]) : undefined,
|
|
107
125
|
description: desc(m[4]),
|
|
108
126
|
});
|
|
@@ -110,77 +128,118 @@ export function parseLine(text, location) {
|
|
|
110
128
|
// ── @exposes ──
|
|
111
129
|
if ((m = trimmed.match(PATTERNS.exposes))) {
|
|
112
130
|
return ok({
|
|
113
|
-
...base, verb: 'exposes', asset: m[1], threat: resolveRef(m[2]),
|
|
131
|
+
...base, verb: 'exposes', asset: resolveRef(m[1]), threat: resolveRef(m[2]),
|
|
132
|
+
severity: m[3] ? resolveSeverity(m[3]) : undefined,
|
|
133
|
+
external_refs: extractExternalRefs(m[4]), description: desc(m[5]),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// ── @confirmed ──
|
|
137
|
+
if ((m = trimmed.match(PATTERNS.confirmed))) {
|
|
138
|
+
return ok({
|
|
139
|
+
...base, verb: 'confirmed', threat: resolveRef(m[1]), asset: resolveRef(m[2]),
|
|
114
140
|
severity: m[3] ? resolveSeverity(m[3]) : undefined,
|
|
115
141
|
external_refs: extractExternalRefs(m[4]), description: desc(m[5]),
|
|
116
142
|
});
|
|
117
143
|
}
|
|
118
144
|
// ── @accepts ──
|
|
119
145
|
if ((m = trimmed.match(PATTERNS.accepts)) || (m = trimmed.match(PATTERNS.accepts_v1))) {
|
|
120
|
-
return ok({ ...base, verb: 'accepts', threat: resolveRef(m[1]), asset: m[2], description: desc(m[3]) });
|
|
146
|
+
return ok({ ...base, verb: 'accepts', threat: resolveRef(m[1]), asset: resolveRef(m[2]), description: desc(m[3]) });
|
|
121
147
|
}
|
|
122
148
|
// ── @transfers ──
|
|
123
149
|
if ((m = trimmed.match(PATTERNS.transfers))) {
|
|
124
150
|
return ok({
|
|
125
151
|
...base, verb: 'transfers', threat: resolveRef(m[1]),
|
|
126
|
-
source: m[2], target: m[3], description: desc(m[4]),
|
|
152
|
+
source: resolveRef(m[2]), target: resolveRef(m[3]), description: desc(m[4]),
|
|
127
153
|
});
|
|
128
154
|
}
|
|
129
155
|
// ── @flows ──
|
|
156
|
+
// Single-hop `A -> B` is a chain of length 2 producing one flow.
|
|
157
|
+
// Multi-hop `A -> B -> C -> D` is treated as syntactic sugar for N-1
|
|
158
|
+
// pairwise flows — each emitted flow shares the mechanism, description,
|
|
159
|
+
// and source location with every other hop in the chain.
|
|
130
160
|
if ((m = trimmed.match(PATTERNS.flows))) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
// Use matchAll instead of split so quoted refs containing literal
|
|
162
|
+
// `->` sequences (e.g. `"step1 -> step2"`) aren't shredded by the
|
|
163
|
+
// arrow separator. The outer regex has already validated chain shape.
|
|
164
|
+
const participants = [...m[1].matchAll(new RegExp(ASSET_REF, 'g'))]
|
|
165
|
+
.map(mm => resolveRef(mm[0]));
|
|
166
|
+
const mechanism = m[2]?.trim();
|
|
167
|
+
const description = desc(m[3]);
|
|
168
|
+
const flows = [];
|
|
169
|
+
for (let i = 0; i < participants.length - 1; i++) {
|
|
170
|
+
flows.push({
|
|
171
|
+
...base, verb: 'flows',
|
|
172
|
+
source: participants[i], target: participants[i + 1],
|
|
173
|
+
mechanism, description,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return okMulti(flows);
|
|
135
177
|
}
|
|
136
178
|
// ── @boundary ──
|
|
137
179
|
if ((m = trimmed.match(PATTERNS.boundary))) {
|
|
138
180
|
return ok({
|
|
139
|
-
...base, verb: 'boundary', asset_a: m[1], asset_b: m[2],
|
|
181
|
+
...base, verb: 'boundary', asset_a: resolveRef(m[1]), asset_b: resolveRef(m[2]),
|
|
140
182
|
id: m[3], description: desc(m[4]),
|
|
141
183
|
});
|
|
142
184
|
}
|
|
143
185
|
// ── @boundary pipe shorthand: @boundary A | B ──
|
|
144
186
|
if ((m = trimmed.match(PATTERNS.boundary_pipe))) {
|
|
145
187
|
return ok({
|
|
146
|
-
...base, verb: 'boundary', asset_a: m[1], asset_b: m[2],
|
|
188
|
+
...base, verb: 'boundary', asset_a: resolveRef(m[1]), asset_b: resolveRef(m[2]),
|
|
147
189
|
id: m[3], description: desc(m[4]),
|
|
148
190
|
});
|
|
149
191
|
}
|
|
150
192
|
// ── @connects (v1 → flows) ──
|
|
151
193
|
if ((m = trimmed.match(PATTERNS.connects_v1))) {
|
|
152
194
|
return ok({
|
|
153
|
-
...base, verb: 'flows', source: m[1], target: m[2], description: desc(m[3]),
|
|
195
|
+
...base, verb: 'flows', source: resolveRef(m[1]), target: resolveRef(m[2]), description: desc(m[3]),
|
|
154
196
|
});
|
|
155
197
|
}
|
|
156
198
|
// ── @validates ──
|
|
157
199
|
if ((m = trimmed.match(PATTERNS.validates))) {
|
|
158
|
-
return ok({ ...base, verb: 'validates', control: resolveRef(m[1]), asset: m[2], description: desc(m[3]) });
|
|
200
|
+
return ok({ ...base, verb: 'validates', control: resolveRef(m[1]), asset: resolveRef(m[2]), description: desc(m[3]) });
|
|
159
201
|
}
|
|
160
202
|
// ── @audit / @review (v1) ──
|
|
161
203
|
if ((m = trimmed.match(PATTERNS.audit)) || (m = trimmed.match(PATTERNS.review_v1))) {
|
|
162
|
-
return ok({ ...base, verb: 'audit', asset: m[1], description: desc(m[2]) });
|
|
204
|
+
return ok({ ...base, verb: 'audit', asset: resolveRef(m[1]), description: desc(m[2]) });
|
|
163
205
|
}
|
|
164
206
|
// ── @owns ──
|
|
165
207
|
if ((m = trimmed.match(PATTERNS.owns))) {
|
|
166
|
-
return ok({ ...base, verb: 'owns', owner: m[1], asset: m[2], description: desc(m[3]) });
|
|
208
|
+
return ok({ ...base, verb: 'owns', owner: m[1], asset: resolveRef(m[2]), description: desc(m[3]) });
|
|
167
209
|
}
|
|
168
210
|
// ── @handles ──
|
|
169
211
|
if ((m = trimmed.match(PATTERNS.handles))) {
|
|
170
212
|
return ok({
|
|
171
213
|
...base, verb: 'handles',
|
|
172
214
|
classification: m[1].toLowerCase(),
|
|
173
|
-
asset: m[2], description: desc(m[3]),
|
|
215
|
+
asset: resolveRef(m[2]), description: desc(m[3]),
|
|
174
216
|
});
|
|
175
217
|
}
|
|
176
218
|
// ── @assumes ──
|
|
177
219
|
if ((m = trimmed.match(PATTERNS.assumes))) {
|
|
178
|
-
return ok({ ...base, verb: 'assumes', asset: m[1], description: desc(m[2]) });
|
|
220
|
+
return ok({ ...base, verb: 'assumes', asset: resolveRef(m[1]), description: desc(m[2]) });
|
|
221
|
+
}
|
|
222
|
+
// ── @feature ──
|
|
223
|
+
if ((m = trimmed.match(PATTERNS.feature))) {
|
|
224
|
+
return ok({ ...base, verb: 'feature', feature: unescapeDescription(m[1]), description: desc(m[2]) });
|
|
179
225
|
}
|
|
180
226
|
// ── @comment ──
|
|
181
227
|
if ((m = trimmed.match(PATTERNS.comment))) {
|
|
182
228
|
return ok({ ...base, verb: 'comment', description: desc(m[1]) });
|
|
183
229
|
}
|
|
230
|
+
// ── @source ──
|
|
231
|
+
if ((m = trimmed.match(PATTERNS.source))) {
|
|
232
|
+
return {
|
|
233
|
+
annotation: null,
|
|
234
|
+
diagnostic: null,
|
|
235
|
+
isContinuation: false,
|
|
236
|
+
sourceDirective: {
|
|
237
|
+
file: m[1],
|
|
238
|
+
line: Number(m[2]),
|
|
239
|
+
symbol: m[3] || undefined,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
184
243
|
// ── @shield ──
|
|
185
244
|
if ((m = trimmed.match(PATTERNS.shield_begin))) {
|
|
186
245
|
return ok({ ...base, verb: 'shield:begin', description: desc(m[1]) });
|
|
@@ -195,9 +254,9 @@ export function parseLine(text, location) {
|
|
|
195
254
|
const verbMatch = trimmed.match(/^@(\S+)/);
|
|
196
255
|
if (verbMatch) {
|
|
197
256
|
const knownVerbs = new Set([
|
|
198
|
-
'asset', 'threat', 'control', 'mitigates', 'exposes', 'accepts',
|
|
257
|
+
'asset', 'threat', 'control', 'mitigates', 'exposes', 'confirmed', 'accepts',
|
|
199
258
|
'transfers', 'flows', 'boundary', 'validates', 'audit', 'owns',
|
|
200
|
-
'handles', 'assumes', 'comment', 'shield', 'shield:begin', 'shield:end',
|
|
259
|
+
'handles', 'assumes', 'feature', 'source', 'comment', 'shield', 'shield:begin', 'shield:end',
|
|
201
260
|
// v1 compat
|
|
202
261
|
'review', 'connects',
|
|
203
262
|
]);
|
|
@@ -216,11 +275,26 @@ export function parseLine(text, location) {
|
|
|
216
275
|
}
|
|
217
276
|
}
|
|
218
277
|
// Not a GuardLink annotation (could be @param, @returns, etc.)
|
|
219
|
-
return { annotation: null, diagnostic: null, isContinuation: false };
|
|
278
|
+
return { annotation: null, diagnostic: null, isContinuation: false, sourceDirective: null };
|
|
220
279
|
}
|
|
221
280
|
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
222
281
|
function ok(annotation) {
|
|
223
|
-
return { annotation, diagnostic: null, isContinuation: false };
|
|
282
|
+
return { annotation, diagnostic: null, isContinuation: false, sourceDirective: null };
|
|
283
|
+
}
|
|
284
|
+
/** Like ok(), but for parser branches that emit multiple annotations from
|
|
285
|
+
* one line (currently only multi-hop @flows chains). The first annotation
|
|
286
|
+
* becomes the primary `annotation`; the remainder go in `extraAnnotations`
|
|
287
|
+
* so the call site can push them all and update lastAnnotation correctly. */
|
|
288
|
+
function okMulti(annotations) {
|
|
289
|
+
if (annotations.length === 0) {
|
|
290
|
+
return { annotation: null, diagnostic: null, isContinuation: false };
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
annotation: annotations[0],
|
|
294
|
+
extraAnnotations: annotations.length > 1 ? annotations.slice(1) : undefined,
|
|
295
|
+
diagnostic: null,
|
|
296
|
+
isContinuation: false,
|
|
297
|
+
};
|
|
224
298
|
}
|
|
225
299
|
function desc(raw) {
|
|
226
300
|
if (!raw)
|