project-tiny-context-harness 0.8.0 → 0.8.2

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 (119) hide show
  1. package/README.md +40 -32
  2. package/assets/README.md +141 -60
  3. package/assets/README.zh-CN.md +48 -40
  4. package/assets/agents/AGENTS_CORE.md +16 -12
  5. package/assets/context_templates/architecture.md +5 -5
  6. package/assets/context_templates/area.md +5 -5
  7. package/assets/context_templates/global.md +6 -6
  8. package/assets/context_templates/screen-contract.md +18 -18
  9. package/assets/github/harness.yml +2 -2
  10. package/assets/skills/context_development_engineer/SKILL.md +7 -5
  11. package/assets/skills/context_full_project_export/SKILL.md +42 -42
  12. package/assets/skills/context_uiux_design/SKILL.md +27 -13
  13. package/assets/skills/design-resource-authoring/SKILL.md +7 -7
  14. package/assets/skills/design-resource-authoring/references/downstream-handoff.md +12 -12
  15. package/assets/skills/design-resource-authoring/references/open-design-provider.md +21 -21
  16. package/assets/skills/design-resource-authoring/references/resource-selection.md +3 -3
  17. package/assets/skills/long-task-workflow/SKILL.md +17 -17
  18. package/assets/skills/long-task-workflow/agents/openai.yaml +4 -4
  19. package/assets/skills/long-task-workflow/references/authority-lifecycle.md +11 -9
  20. package/assets/skills/long-task-workflow/references/contract-authoring.md +19 -15
  21. package/assets/skills/long-task-workflow/references/evidence-design.md +18 -17
  22. package/assets/skills/long-task-workflow/references/source-authoring.md +15 -12
  23. package/assets/skills/normal-long-task/SKILL.md +12 -12
  24. package/dist/commands/design-resource.js +2 -2
  25. package/dist/commands/index.js +26 -26
  26. package/dist/commands/long-task-authoring.js +83 -9
  27. package/dist/commands/long-task.js +15 -15
  28. package/dist/lib/context-graph-snapshot.js +7 -0
  29. package/dist/lib/design-md.js +5 -4
  30. package/dist/lib/design-resource-handoff-shape-evidence.js +3 -0
  31. package/dist/lib/design-resource-handoff-shape-primitives.js +1 -3
  32. package/dist/lib/design-resource-handoff-types.d.ts +2 -0
  33. package/dist/lib/design-resource-handoff-validation-coverage.js +1 -0
  34. package/dist/lib/design-resource-handoff-validation-structure.js +2 -0
  35. package/dist/lib/doctor.js +2 -2
  36. package/dist/lib/execution-target-capabilities.d.ts +2 -0
  37. package/dist/lib/execution-target-capabilities.js +31 -0
  38. package/dist/lib/long-task-acceptance-shape.js +81 -20
  39. package/dist/lib/long-task-applicability-shape.d.ts +4 -0
  40. package/dist/lib/long-task-applicability-shape.js +54 -0
  41. package/dist/lib/long-task-assertions-v2.js +8 -0
  42. package/dist/lib/long-task-authoring-preflight-diagnostics.js +5 -0
  43. package/dist/lib/long-task-authoring-preflight-repair-order.js +3 -0
  44. package/dist/lib/long-task-authoring-preflight.js +12 -8
  45. package/dist/lib/long-task-authority-materials.js +46 -4
  46. package/dist/lib/long-task-authority-policy.d.ts +54 -0
  47. package/dist/lib/long-task-authority-policy.js +61 -0
  48. package/dist/lib/long-task-authority-types.d.ts +12 -2
  49. package/dist/lib/long-task-authority.js +9 -2
  50. package/dist/lib/long-task-check-shape.js +6 -1
  51. package/dist/lib/long-task-claim-definitions.js +27 -42
  52. package/dist/lib/long-task-claims.js +275 -146
  53. package/dist/lib/long-task-contract-types.d.ts +42 -8
  54. package/dist/lib/long-task-control-fields.d.ts +12 -0
  55. package/dist/lib/long-task-control-fields.js +66 -0
  56. package/dist/lib/long-task-control-types.d.ts +1 -0
  57. package/dist/lib/long-task-control-types.js +1 -0
  58. package/dist/lib/long-task-counterfactual-claim-policy.js +7 -0
  59. package/dist/lib/long-task-counterfactual-sandbox.js +1 -2
  60. package/dist/lib/long-task-counterfactual-types.d.ts +12 -0
  61. package/dist/lib/long-task-delivery-compiler.js +1 -0
  62. package/dist/lib/long-task-delivery-preflight.js +24 -0
  63. package/dist/lib/long-task-delivery-shape.d.ts +1 -0
  64. package/dist/lib/long-task-delivery-shape.js +1 -0
  65. package/dist/lib/long-task-delivery-types.d.ts +1 -0
  66. package/dist/lib/long-task-delivery-types.js +1 -0
  67. package/dist/lib/long-task-delivery-validation.js +46 -0
  68. package/dist/lib/long-task-design-resource-handoff.js +48 -7
  69. package/dist/lib/long-task-evidence-capability-codec.js +37 -0
  70. package/dist/lib/long-task-evidence-capability-policy.js +80 -38
  71. package/dist/lib/long-task-evidence-capability-runtime.js +36 -0
  72. package/dist/lib/long-task-evidence-capability-types.d.ts +13 -1
  73. package/dist/lib/long-task-evidence-findings.js +3 -0
  74. package/dist/lib/long-task-evidence-sensitivity-policy.js +38 -0
  75. package/dist/lib/long-task-evidence-v2.js +7 -1
  76. package/dist/lib/long-task-final-integrity.d.ts +16 -0
  77. package/dist/lib/long-task-final-integrity.js +98 -0
  78. package/dist/lib/long-task-final-v2.js +17 -34
  79. package/dist/lib/long-task-outcome-parser.js +22 -4
  80. package/dist/lib/long-task-playwright-case-evidence.d.ts +2 -0
  81. package/dist/lib/long-task-playwright-case-evidence.js +10 -2
  82. package/dist/lib/long-task-playwright-evidence.d.ts +1 -0
  83. package/dist/lib/long-task-playwright-evidence.js +32 -0
  84. package/dist/lib/long-task-product-shape.d.ts +3 -1
  85. package/dist/lib/long-task-product-shape.js +100 -4
  86. package/dist/lib/long-task-requirement-shape.js +3 -0
  87. package/dist/lib/long-task-root-shape.js +12 -6
  88. package/dist/lib/long-task-runner-freeze.js +16 -0
  89. package/dist/lib/long-task-runtime-types.d.ts +11 -2
  90. package/dist/lib/long-task-semantic-assurance-policy.d.ts +4 -0
  91. package/dist/lib/long-task-semantic-assurance-policy.js +52 -0
  92. package/dist/lib/long-task-semantic-contract-types.d.ts +18 -1
  93. package/dist/lib/long-task-semantic-drift-migration.js +33 -2
  94. package/dist/lib/long-task-semantic-mutation.d.ts +7 -0
  95. package/dist/lib/long-task-semantic-mutation.js +81 -0
  96. package/dist/lib/long-task-shape-primitives.d.ts +1 -1
  97. package/dist/lib/long-task-shape-primitives.js +1 -0
  98. package/dist/lib/long-task-source-authority-types.d.ts +2 -0
  99. package/dist/lib/long-task-source-item-parser.d.ts +1 -1
  100. package/dist/lib/long-task-source-item-parser.js +150 -104
  101. package/dist/lib/long-task-source-markers.d.ts +12 -0
  102. package/dist/lib/long-task-source-markers.js +146 -0
  103. package/dist/lib/long-task-source-target-continuity.d.ts +1 -1
  104. package/dist/lib/long-task-source-target-continuity.js +80 -55
  105. package/dist/lib/long-task-source-target-index.js +7 -29
  106. package/dist/lib/long-task-status-v2.d.ts +1 -0
  107. package/dist/lib/long-task-status-v2.js +2 -2
  108. package/dist/lib/long-task-target-policy.js +20 -0
  109. package/dist/lib/long-task-ui-design-policy.js +27 -1
  110. package/dist/lib/long-task-ui-surface-shape.js +21 -1
  111. package/dist/lib/long-task-ui-surface-types.d.ts +7 -0
  112. package/dist/lib/long-task-verification-preview.d.ts +10 -0
  113. package/dist/lib/long-task-verifier-dependency-closure.d.ts +3 -0
  114. package/dist/lib/long-task-verifier-dependency-closure.js +179 -0
  115. package/dist/lib/long-task-verifier-v2.js +12 -2
  116. package/dist/lib/long-task-workspace.js +5 -1
  117. package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +99 -25
  118. package/migrations/README.md +8 -8
  119. package/package.json +84 -84
@@ -1,112 +1,158 @@
1
- import { RISK_FACT_NAMES } from "./long-task-risk-types.js";
2
- import { sha256Hex } from "./strict-codec.js";
3
- const KINDS = new Set([
4
- "outcome_result",
5
- "requirement",
6
- "control",
7
- "acceptance",
8
- "technical_obligation",
9
- "non_completing",
10
- "non_goal",
11
- "forbidden_shortcut",
12
- "risk_fact",
13
- "external_confirmation",
14
- "decision",
15
- ]);
16
- const RISK_FACTS = new Set(RISK_FACT_NAMES);
17
- const START = /^\s*<!--\s*ty-source-item:start\s+(.+?)\s*-->\s*$/u;
18
- const END = /^\s*<!--\s*ty-source-item:end\s*-->\s*$/u;
1
+ import { parseDesignResourceHandoffShape } from "./design-resource-handoff-shape.js";
2
+ import { normalizeSourceItemText, parseSourceBackgroundMarker, parseSourceItemStartMarker, SOURCE_BACKGROUND_END, SOURCE_BACKGROUND_START, SOURCE_ITEM_END, SOURCE_ITEM_START, validateSourceBackgroundLine, } from "./long-task-source-markers.js";
3
+ import { parseStrictYaml, sha256Hex } from "./strict-codec.js";
4
+ const DESIGN_RESOURCE_START = /^\s*```yaml[ \t]+design-resource-handoff-v1[ \t]*$/u;
5
+ const FORMAL_BLOCK_END = /^\s*```[ \t]*$/u;
19
6
  export function parseSourceItems(sourcePath, content) {
20
- const items = [];
21
- const seen = new Set();
22
- let open = null;
7
+ const state = sourceParseState(sourcePath);
23
8
  const lines = content.replace(/\r\n?/gu, "\n").split("\n");
24
- for (const [index, line] of lines.entries()) {
25
- const start = START.exec(line);
26
- if (start) {
27
- if (open)
28
- throw new Error(`source_item_nested_or_overlapping:${sourcePath}:${open.key}:${index + 1}`);
29
- const marker = parseStartMarker(sourcePath, index + 1, start[1]);
30
- open = { ...marker, lines: [], line: index + 1 };
31
- continue;
32
- }
33
- if (END.test(line)) {
34
- if (!open)
35
- throw new Error(`source_item_end_without_start:${sourcePath}:${index + 1}`);
36
- const normalizedText = normalizeSourceItemText(open.lines.join("\n"));
37
- if (!normalizedText)
38
- throw new Error(`source_item_empty:${sourcePath}:${open.key}`);
39
- if (seen.has(open.key))
40
- throw new Error(`source_item_key_duplicate:${open.key}`);
41
- seen.add(open.key);
42
- items.push({
43
- key: open.key,
44
- kind: open.kind,
45
- source_path: sourcePath,
46
- normalized_text: normalizedText,
47
- text_sha256: sha256Hex(normalizedText),
48
- ...(open.risk_semantics ? { risk_semantics: open.risk_semantics } : {}),
49
- });
50
- open = null;
51
- continue;
52
- }
53
- if (line.includes("ty-source-item:start") ||
54
- line.includes("ty-source-item:end"))
55
- throw new Error(`source_item_marker_invalid:${sourcePath}:${index + 1}`);
56
- if (open)
57
- open.lines.push(line);
58
- }
59
- if (open)
60
- throw new Error(`source_item_unclosed:${sourcePath}:${open.key}:${open.line}`);
61
- return items;
9
+ for (const [index, line] of lines.entries())
10
+ consumeSourceLine(state, line, index + 1);
11
+ assertSourceSectionsClosed(state);
12
+ return state.items;
13
+ }
14
+ function sourceParseState(sourcePath) {
15
+ return {
16
+ sourcePath,
17
+ items: [],
18
+ seen: new Set(),
19
+ seenBackground: new Set(),
20
+ open: null,
21
+ background: null,
22
+ formalBlock: null,
23
+ designResourceBlocks: 0,
24
+ };
62
25
  }
63
- function parseStartMarker(sourcePath, line, declaration) {
64
- const attributes = new Map();
65
- for (const token of declaration.trim().split(/\s+/u)) {
66
- const match = /^([a-z_]+)=([^\s=]+)$/u.exec(token);
67
- if (!match)
68
- throw new Error(`source_item_marker_invalid:${sourcePath}:${line}`);
69
- if (attributes.has(match[1]))
70
- throw new Error(`source_item_marker_attribute_duplicate:${sourcePath}:${line}:${match[1]}`);
71
- attributes.set(match[1], match[2]);
26
+ function consumeSourceLine(state, line, lineNumber) {
27
+ if (consumeOpenFormalBlock(state, line))
28
+ return;
29
+ if (startFormalBlock(state, line, lineNumber))
30
+ return;
31
+ if (consumeSourceItemBoundary(state, line, lineNumber))
32
+ return;
33
+ if (consumeBackgroundBoundary(state, line, lineNumber))
34
+ return;
35
+ consumeOwnedText(state, line, lineNumber);
36
+ }
37
+ function consumeOpenFormalBlock(state, line) {
38
+ if (!state.formalBlock)
39
+ return false;
40
+ if (!FORMAL_BLOCK_END.test(line)) {
41
+ state.formalBlock.lines.push(line);
42
+ return true;
72
43
  }
73
- for (const name of attributes.keys())
74
- if (!["key", "kind", "fact", "outcome"].includes(name))
75
- throw new Error(`source_item_marker_attribute_unknown:${sourcePath}:${line}:${name}`);
76
- const key = attributes.get("key") ?? "";
77
- const kind = (attributes.get("kind") ?? "");
78
- if (!/^[a-z0-9][a-z0-9-]*$/u.test(key) || !KINDS.has(kind))
79
- throw new Error(`source_item_marker_invalid:${sourcePath}:${line}`);
80
- const fact = attributes.get("fact");
81
- const outcome = attributes.get("outcome");
82
- if (kind !== "risk_fact") {
83
- if (fact !== undefined || outcome !== undefined)
84
- throw new Error(`source_item_marker_attributes_forbidden:${sourcePath}:${key}:${kind}`);
85
- return { key, kind };
44
+ try {
45
+ parseDesignResourceHandoffShape(parseStrictYaml(state.formalBlock.lines.join("\n")));
86
46
  }
87
- if (!fact || !outcome)
88
- throw new Error(`source_item_risk_semantics_required:${sourcePath}:${key}`);
89
- if (!RISK_FACTS.has(fact))
90
- throw new Error(`source_item_risk_fact_invalid:${sourcePath}:${key}:${fact}`);
91
- if (!/^[a-z0-9][a-z0-9-]*$/u.test(outcome))
92
- throw new Error(`source_item_risk_outcome_invalid:${sourcePath}:${key}:${outcome}`);
93
- return {
94
- key,
95
- kind,
96
- risk_semantics: {
97
- fact: fact,
98
- affected_outcome: outcome,
99
- },
47
+ catch (error) {
48
+ const message = error instanceof Error ? error.message : String(error);
49
+ throw new Error(`source_formal_block_invalid:${state.sourcePath}:${state.formalBlock.line}:${message}`);
50
+ }
51
+ state.formalBlock = null;
52
+ return true;
53
+ }
54
+ function startFormalBlock(state, line, lineNumber) {
55
+ if (!DESIGN_RESOURCE_START.test(line))
56
+ return false;
57
+ assertNoOpenOwnedSection(state, lineNumber);
58
+ state.designResourceBlocks += 1;
59
+ if (state.designResourceBlocks > 1)
60
+ throw new Error(`source_formal_block_duplicate:${state.sourcePath}:design-resource-handoff-v1`);
61
+ state.formalBlock = {
62
+ owner: "design-resource-handoff-v1",
63
+ lines: [],
64
+ line: lineNumber,
100
65
  };
66
+ return true;
67
+ }
68
+ function consumeSourceItemBoundary(state, line, lineNumber) {
69
+ const start = SOURCE_ITEM_START.exec(line);
70
+ if (start) {
71
+ assertNoOpenOwnedSection(state, lineNumber);
72
+ state.open = {
73
+ ...parseSourceItemStartMarker(state.sourcePath, lineNumber, start[1]),
74
+ lines: [],
75
+ line: lineNumber,
76
+ };
77
+ return true;
78
+ }
79
+ if (!SOURCE_ITEM_END.test(line))
80
+ return false;
81
+ if (!state.open)
82
+ throw new Error(`source_item_end_without_start:${state.sourcePath}:${lineNumber}`);
83
+ closeSourceItem(state);
84
+ return true;
85
+ }
86
+ function closeSourceItem(state) {
87
+ const open = state.open;
88
+ const normalizedText = normalizeSourceItemText(open.lines.join("\n"));
89
+ if (!normalizedText)
90
+ throw new Error(`source_item_empty:${state.sourcePath}:${open.key}`);
91
+ if (state.seen.has(open.key))
92
+ throw new Error(`source_item_key_duplicate:${open.key}`);
93
+ state.seen.add(open.key);
94
+ state.items.push({
95
+ key: open.key,
96
+ kind: open.kind,
97
+ ...(open.aspect ? { aspect: open.aspect } : {}),
98
+ source_path: state.sourcePath,
99
+ normalized_text: normalizedText,
100
+ text_sha256: sha256Hex(normalizedText),
101
+ ...(open.risk_semantics ? { risk_semantics: open.risk_semantics } : {}),
102
+ });
103
+ state.open = null;
104
+ }
105
+ function consumeBackgroundBoundary(state, line, lineNumber) {
106
+ const start = SOURCE_BACKGROUND_START.exec(line);
107
+ if (start) {
108
+ assertNoOpenOwnedSection(state, lineNumber);
109
+ const declaration = parseSourceBackgroundMarker(state.sourcePath, lineNumber, start[1]);
110
+ if (state.seenBackground.has(declaration.key))
111
+ throw new Error(`source_background_key_duplicate:${declaration.key}`);
112
+ state.seenBackground.add(declaration.key);
113
+ state.background = {
114
+ ...declaration,
115
+ substantiveLines: 0,
116
+ line: lineNumber,
117
+ };
118
+ return true;
119
+ }
120
+ if (!SOURCE_BACKGROUND_END.test(line))
121
+ return false;
122
+ if (!state.background)
123
+ throw new Error(`source_background_end_without_start:${state.sourcePath}:${lineNumber}`);
124
+ if (!state.background.substantiveLines)
125
+ throw new Error(`source_background_empty:${state.sourcePath}:${state.background.key}`);
126
+ state.background = null;
127
+ return true;
128
+ }
129
+ function assertNoOpenOwnedSection(state, lineNumber) {
130
+ if (!state.open && !state.background)
131
+ return;
132
+ throw new Error(`source_section_nested_or_overlapping:${state.sourcePath}:${state.open?.key ?? state.background?.key}:${lineNumber}`);
133
+ }
134
+ function consumeOwnedText(state, line, lineNumber) {
135
+ if (line.includes("ty-source-item:start") ||
136
+ line.includes("ty-source-item:end") ||
137
+ line.includes("ty-source-background:start") ||
138
+ line.includes("ty-source-background:end"))
139
+ throw new Error(`source_item_marker_invalid:${state.sourcePath}:${lineNumber}`);
140
+ if (state.open)
141
+ state.open.lines.push(line);
142
+ else if (state.background) {
143
+ validateSourceBackgroundLine(state.sourcePath, state.background.key, state.background.reason, lineNumber, line);
144
+ if (line.trim())
145
+ state.background.substantiveLines += 1;
146
+ }
147
+ else if (line.trim())
148
+ throw new Error(`source_text_unclassified:${state.sourcePath}:${lineNumber}`);
101
149
  }
102
- export function normalizeSourceItemText(value) {
103
- const lines = value
104
- .replace(/\r\n?/gu, "\n")
105
- .split("\n")
106
- .map((line) => line.replace(/[ \t]+$/gu, ""));
107
- while (lines.length && !lines[0].trim())
108
- lines.shift();
109
- while (lines.length && !lines.at(-1).trim())
110
- lines.pop();
111
- return lines.join("\n");
150
+ function assertSourceSectionsClosed(state) {
151
+ if (state.open)
152
+ throw new Error(`source_item_unclosed:${state.sourcePath}:${state.open.key}:${state.open.line}`);
153
+ if (state.background)
154
+ throw new Error(`source_background_unclosed:${state.sourcePath}:${state.background.key}:${state.background.line}`);
155
+ if (state.formalBlock)
156
+ throw new Error(`source_formal_block_unclosed:${state.sourcePath}:${state.formalBlock.owner}:${state.formalBlock.line}`);
112
157
  }
158
+ export { normalizeSourceItemText } from "./long-task-source-markers.js";
@@ -0,0 +1,12 @@
1
+ import type { CompiledSourceItemV2 } from "./long-task-delivery-types.js";
2
+ export declare const SOURCE_ITEM_START: RegExp;
3
+ export declare const SOURCE_ITEM_END: RegExp;
4
+ export declare const SOURCE_BACKGROUND_START: RegExp;
5
+ export declare const SOURCE_BACKGROUND_END: RegExp;
6
+ export declare function parseSourceBackgroundMarker(sourcePath: string, line: number, declaration: string): {
7
+ key: string;
8
+ reason: string;
9
+ };
10
+ export declare function parseSourceItemStartMarker(sourcePath: string, line: number, declaration: string): Pick<CompiledSourceItemV2, "key" | "kind" | "aspect" | "risk_semantics">;
11
+ export declare function validateSourceBackgroundLine(sourcePath: string, blockKey: string, reason: string, lineNumber: number, value: string): void;
12
+ export declare function normalizeSourceItemText(value: string): string;
@@ -0,0 +1,146 @@
1
+ import { RISK_FACT_NAMES } from "./long-task-risk-types.js";
2
+ const KINDS = new Set([
3
+ "outcome_result",
4
+ "requirement",
5
+ "control",
6
+ "acceptance",
7
+ "technical_obligation",
8
+ "non_completing",
9
+ "non_goal",
10
+ "forbidden_shortcut",
11
+ "risk_fact",
12
+ "external_confirmation",
13
+ "decision",
14
+ ]);
15
+ const RISK_FACTS = new Set(RISK_FACT_NAMES);
16
+ const BACKGROUND_REASONS = new Set(["markdown-structure", "provenance"]);
17
+ export const SOURCE_ITEM_START = /^\s*<!--\s*ty-source-item:start\s+(.+?)\s*-->\s*$/u;
18
+ export const SOURCE_ITEM_END = /^\s*<!--\s*ty-source-item:end\s*-->\s*$/u;
19
+ export const SOURCE_BACKGROUND_START = /^\s*<!--\s*ty-source-background:start\s+(.+?)\s*-->\s*$/u;
20
+ export const SOURCE_BACKGROUND_END = /^\s*<!--\s*ty-source-background:end\s*-->\s*$/u;
21
+ export function parseSourceBackgroundMarker(sourcePath, line, declaration) {
22
+ const attributes = markerAttributes(sourcePath, line, declaration, "background");
23
+ for (const name of attributes.keys())
24
+ if (!["key", "reason"].includes(name))
25
+ throw new Error(`source_background_marker_attribute_unknown:${sourcePath}:${line}:${name}`);
26
+ const key = attributes.get("key") ?? "";
27
+ const reason = attributes.get("reason") ?? "";
28
+ if (!/^[a-z0-9][a-z0-9-]*$/u.test(key) ||
29
+ !/^[a-z0-9][a-z0-9-]*$/u.test(reason))
30
+ throw new Error(`source_background_marker_invalid:${sourcePath}:${line}`);
31
+ if (!BACKGROUND_REASONS.has(reason))
32
+ throw new Error(`source_background_reason_unknown:${sourcePath}:${line}:${reason}`);
33
+ return { key, reason };
34
+ }
35
+ export function parseSourceItemStartMarker(sourcePath, line, declaration) {
36
+ const attributes = markerAttributes(sourcePath, line, declaration, "item");
37
+ for (const name of attributes.keys())
38
+ if (!["key", "kind", "aspect", "fact", "outcome"].includes(name))
39
+ throw new Error(`source_item_marker_attribute_unknown:${sourcePath}:${line}:${name}`);
40
+ const key = attributes.get("key") ?? "";
41
+ const kind = (attributes.get("kind") ?? "");
42
+ if (!/^[a-z0-9][a-z0-9-]*$/u.test(key) || !KINDS.has(kind))
43
+ throw new Error(`source_item_marker_invalid:${sourcePath}:${line}`);
44
+ return sourceItemMarkerWithRiskSemantics(sourcePath, key, kind, attributes);
45
+ }
46
+ function markerAttributes(sourcePath, line, declaration, marker) {
47
+ const attributes = new Map();
48
+ for (const token of declaration.trim().split(/\s+/u)) {
49
+ const match = /^([a-z_]+)=([^\s=]+)$/u.exec(token);
50
+ if (!match)
51
+ throw new Error(`source_${marker}_marker_invalid:${sourcePath}:${line}`);
52
+ if (attributes.has(match[1]))
53
+ throw new Error(`source_${marker}_marker_attribute_duplicate:${sourcePath}:${line}:${match[1]}`);
54
+ attributes.set(match[1], match[2]);
55
+ }
56
+ return attributes;
57
+ }
58
+ function sourceItemMarkerWithRiskSemantics(sourcePath, key, kind, attributes) {
59
+ const fact = attributes.get("fact");
60
+ const outcome = attributes.get("outcome");
61
+ const aspect = attributes.get("aspect");
62
+ if (aspect !== undefined) {
63
+ if (kind !== "technical_obligation" || aspect !== "architecture")
64
+ throw new Error(`source_item_aspect_invalid:${sourcePath}:${key}:${kind}:${aspect}`);
65
+ }
66
+ if (kind !== "risk_fact") {
67
+ if (fact !== undefined || outcome !== undefined)
68
+ throw new Error(`source_item_marker_attributes_forbidden:${sourcePath}:${key}:${kind}`);
69
+ return {
70
+ key,
71
+ kind,
72
+ ...(aspect === "architecture" ? { aspect } : {}),
73
+ };
74
+ }
75
+ if (aspect !== undefined)
76
+ throw new Error(`source_item_marker_attributes_forbidden:${sourcePath}:${key}:${kind}`);
77
+ if (!fact || !outcome)
78
+ throw new Error(`source_item_risk_semantics_required:${sourcePath}:${key}`);
79
+ if (!RISK_FACTS.has(fact))
80
+ throw new Error(`source_item_risk_fact_invalid:${sourcePath}:${key}:${fact}`);
81
+ if (!/^[a-z0-9][a-z0-9-]*$/u.test(outcome))
82
+ throw new Error(`source_item_risk_outcome_invalid:${sourcePath}:${key}:${outcome}`);
83
+ return {
84
+ key,
85
+ kind,
86
+ risk_semantics: {
87
+ fact: fact,
88
+ affected_outcome: outcome,
89
+ },
90
+ };
91
+ }
92
+ export function validateSourceBackgroundLine(sourcePath, blockKey, reason, lineNumber, value) {
93
+ if (!value.trim())
94
+ return;
95
+ const valid = reason === "markdown-structure"
96
+ ? isMarkdownStructure(value)
97
+ : reason === "provenance"
98
+ ? isStructuredProvenance(value)
99
+ : false;
100
+ if (!valid)
101
+ throw new Error(`source_background_content_invalid:${sourcePath}:${blockKey}:${reason}:${lineNumber}`);
102
+ }
103
+ function isMarkdownStructure(value) {
104
+ return (/^\s*<a\s+(?:id|name)="[A-Za-z0-9_.:-]+"\s*><\/a>\s*$/u.test(value) ||
105
+ /^\s{0,3}(?:-{3,}|\*{3,}|_{3,})\s*$/u.test(value));
106
+ }
107
+ function isStructuredProvenance(value) {
108
+ const declaration = /^\s*<!--\s*ty-source-provenance\s+(.+?)\s*-->\s*$/u.exec(value)?.[1];
109
+ if (!declaration)
110
+ return false;
111
+ const attributes = new Map();
112
+ for (const token of declaration.split(/\s+/u)) {
113
+ const match = /^([a-z][a-z0-9_-]*)=([^\s=>]+)$/u.exec(token);
114
+ if (!match || attributes.has(match[1]))
115
+ return false;
116
+ attributes.set(match[1], match[2]);
117
+ }
118
+ if ([...attributes.keys()].some((name) => !["input", "mode", "source", "sha256"].includes(name)))
119
+ return false;
120
+ const input = attributes.get("input") ?? "";
121
+ const mode = attributes.get("mode") ?? "";
122
+ const source = attributes.get("source");
123
+ const digest = attributes.get("sha256");
124
+ if (!/^[a-z0-9][a-z0-9-]*$/u.test(input))
125
+ return false;
126
+ if (!["direct", "derived", "delegated", "evidence-backed"].includes(mode))
127
+ return false;
128
+ if (mode !== "direct" && !source)
129
+ return false;
130
+ if (source && !/^[a-z0-9][a-z0-9-]*$/u.test(source))
131
+ return false;
132
+ if (digest && !/^[a-f0-9]{64}$/u.test(digest))
133
+ return false;
134
+ return true;
135
+ }
136
+ export function normalizeSourceItemText(value) {
137
+ const lines = value
138
+ .replace(/\r\n?/gu, "\n")
139
+ .split("\n")
140
+ .map((line) => line.replace(/[ \t]+$/gu, ""));
141
+ while (lines.length && !lines[0].trim())
142
+ lines.shift();
143
+ while (lines.length && !lines.at(-1).trim())
144
+ lines.pop();
145
+ return lines.join("\n");
146
+ }
@@ -1,4 +1,4 @@
1
1
  import type { CompiledSourceItemV2, DeliveryContractV2 } from "./long-task-delivery-types.js";
2
- export declare function validateSourceTargetContinuity(contract: DeliveryContractV2, items: CompiledSourceItemV2[], report?: ValidationReporter): void;
3
2
  type ValidationReporter = (message: string) => void;
3
+ export declare function validateSourceTargetContinuity(contract: DeliveryContractV2, items: CompiledSourceItemV2[], report?: ValidationReporter): void;
4
4
  export {};
@@ -1,71 +1,96 @@
1
1
  import { resolveAcceptanceAssertion } from "./long-task-acceptance-reference.js";
2
2
  import { buildCanonicalSourceTargetIndex, sourceKindForTarget, } from "./long-task-source-target-index.js";
3
3
  export function validateSourceTargetContinuity(contract, items, report) {
4
- const itemByKey = new Map(items.map((item) => [item.key, item]));
5
- const targets = buildCanonicalSourceTargetIndex(contract);
6
- const owners = new Map();
7
- const sourceBackedOutcomeClaims = new Set();
8
- const sourceBackedGlobalClaims = new Set();
9
- const acceptanceBindings = [];
10
- for (const claim of contract.source_claims) {
11
- const item = itemByKey.get(claim.key);
12
- if (!item || claim.disposition.type === "decision_required")
13
- continue;
14
- const refs = dispositionRefs(claim.disposition);
15
- if (refs.length !== 1) {
16
- issue(report, `source_claim_target_ref_count:${claim.key}:${refs.length}`);
17
- continue;
18
- }
19
- const ref = refs[0];
20
- const target = targets.get(ref);
21
- if (!target || sourceKindForTarget(target) !== item.kind) {
22
- issue(report, `source_target_kind_mismatch:${claim.key}:${item.kind}:${target ? sourceKindForTarget(target) : "unknown"}:${ref}`);
23
- continue;
24
- }
25
- if (item.kind === "risk_fact" &&
26
- (target.risk_fact !== item.risk_semantics?.fact ||
27
- target.affected_outcome !== item.risk_semantics?.affected_outcome)) {
28
- issue(report, `source_risk_target_mismatch:${claim.key}:${item.risk_semantics?.fact ?? "missing"}:${item.risk_semantics?.affected_outcome ?? "missing"}:${ref}`);
29
- continue;
30
- }
31
- if (target.normalized_text !== undefined &&
32
- target.normalized_text !== item.normalized_text) {
33
- issue(report, `source_target_statement_mismatch:${claim.key}:${ref}`);
34
- continue;
35
- }
36
- const owner = owners.get(ref);
37
- if (owner) {
38
- issue(report, `source_target_already_owned:${claim.key}:${ref}:${owner}`);
39
- continue;
40
- }
41
- owners.set(ref, claim.key);
42
- if (target.outcome_key &&
43
- [
44
- "requirement",
45
- "control",
46
- "technical_obligation",
47
- "non_completing",
48
- "forbidden_shortcut",
49
- ].includes(target.kind))
50
- sourceBackedOutcomeClaims.add(ref);
51
- if (!target.outcome_key &&
52
- ["global_constraint", "non_goal", "forbidden_shortcut"].includes(target.kind))
53
- sourceBackedGlobalClaims.add(ref);
54
- if (target.kind === "acceptance")
55
- acceptanceBindings.push({ claim, target });
4
+ const state = sourceContinuityState(contract, items);
5
+ for (const claim of contract.source_claims)
6
+ validateSourceClaimContinuity(claim, state, report);
7
+ validateAcceptanceBindingContinuity(contract, state, report);
8
+ validateArchitectureContinuity(state, report);
9
+ }
10
+ function sourceContinuityState(contract, items) {
11
+ return {
12
+ itemByKey: new Map(items.map((item) => [item.key, item])),
13
+ targets: buildCanonicalSourceTargetIndex(contract),
14
+ owners: new Map(),
15
+ sourceBackedOutcomeClaims: new Set(),
16
+ sourceBackedGlobalClaims: new Set(),
17
+ acceptanceBindings: [],
18
+ architectureItems: new Set(items
19
+ .filter((item) => item.aspect === "architecture")
20
+ .map((item) => item.key)),
21
+ ownedArchitectureItems: new Set(),
22
+ };
23
+ }
24
+ function validateSourceClaimContinuity(claim, state, report) {
25
+ const item = state.itemByKey.get(claim.key);
26
+ if (!item || claim.disposition.type === "decision_required")
27
+ return;
28
+ const refs = dispositionRefs(claim.disposition);
29
+ if (refs.length !== 1) {
30
+ issue(report, `source_claim_target_ref_count:${claim.key}:${refs.length}`);
31
+ return;
32
+ }
33
+ const ref = refs[0];
34
+ const target = state.targets.get(ref);
35
+ if (!target || sourceKindForTarget(target) !== item.kind) {
36
+ issue(report, `source_target_kind_mismatch:${claim.key}:${item.kind}:${target ? sourceKindForTarget(target) : "unknown"}:${ref}`);
37
+ return;
38
+ }
39
+ if (item.kind === "risk_fact" &&
40
+ (target.risk_fact !== item.risk_semantics?.fact ||
41
+ target.affected_outcome !== item.risk_semantics?.affected_outcome)) {
42
+ issue(report, `source_risk_target_mismatch:${claim.key}:${item.risk_semantics?.fact ?? "missing"}:${item.risk_semantics?.affected_outcome ?? "missing"}:${ref}`);
43
+ return;
56
44
  }
57
- for (const { claim, target } of acceptanceBindings) {
45
+ if (target.normalized_text !== undefined &&
46
+ target.normalized_text !== item.normalized_text) {
47
+ issue(report, `source_target_statement_mismatch:${claim.key}:${ref}`);
48
+ return;
49
+ }
50
+ const owner = state.owners.get(ref);
51
+ if (owner) {
52
+ issue(report, `source_target_already_owned:${claim.key}:${ref}:${owner}`);
53
+ return;
54
+ }
55
+ state.owners.set(ref, claim.key);
56
+ if (state.architectureItems.has(item.key) &&
57
+ target.kind === "technical_obligation")
58
+ state.ownedArchitectureItems.add(item.key);
59
+ if (target.outcome_key &&
60
+ [
61
+ "requirement",
62
+ "control",
63
+ "technical_obligation",
64
+ "non_completing",
65
+ "forbidden_shortcut",
66
+ ].includes(target.kind))
67
+ state.sourceBackedOutcomeClaims.add(ref);
68
+ if (!target.outcome_key &&
69
+ ["global_constraint", "non_goal", "forbidden_shortcut"].includes(target.kind))
70
+ state.sourceBackedGlobalClaims.add(ref);
71
+ if (target.kind === "acceptance")
72
+ state.acceptanceBindings.push({ claim, target });
73
+ }
74
+ function validateAcceptanceBindingContinuity(contract, state, report) {
75
+ for (const { claim, target } of state.acceptanceBindings) {
58
76
  const resolved = resolveAcceptanceAssertion(contract, target.ref);
59
77
  const sourceBacked = resolved?.scope === "outcome" && target.outcome_key
60
78
  ? resolved.assertion.claims.some((localKey) => localKey !== "result" &&
61
- sourceBackedOutcomeClaims.has(`${target.outcome_key}.${localKey}`))
79
+ state.sourceBackedOutcomeClaims.has(`${target.outcome_key}.${localKey}`))
62
80
  : resolved?.scope === "global"
63
- ? resolved.assertion.claims.some((localKey) => sourceBackedGlobalClaims.has(localKey))
81
+ ? resolved.assertion.claims.some((localKey) => state.sourceBackedGlobalClaims.has(localKey))
64
82
  : false;
65
83
  if (!sourceBacked)
66
84
  issue(report, `source_acceptance_without_source_backed_claim:${claim.key}:${target.ref}`);
67
85
  }
68
86
  }
87
+ function validateArchitectureContinuity(state, report) {
88
+ if (!state.architectureItems.size)
89
+ issue(report, "source_architecture_obligation_required");
90
+ for (const key of state.architectureItems)
91
+ if (!state.ownedArchitectureItems.has(key))
92
+ issue(report, `source_architecture_obligation_unmapped:${key}`);
93
+ }
69
94
  function dispositionRefs(disposition) {
70
95
  if (disposition.type === "decision_required")
71
96
  return [];
@@ -1,3 +1,4 @@
1
+ import { controlFieldFacts } from "./long-task-control-fields.js";
1
2
  import { normalizeSourceItemText } from "./long-task-source-item-parser.js";
2
3
  export function buildCanonicalSourceTargetIndex(contract) {
3
4
  const targets = [];
@@ -6,9 +7,12 @@ export function buildCanonicalSourceTargetIndex(contract) {
6
7
  for (const requirement of outcome.product.requirements)
7
8
  targets.push(target(`${outcome.key}.requirement.${requirement.key}`, "requirement", requirement.statement, outcome.key));
8
9
  for (const control of outcome.product.controls)
9
- for (const [field, value] of controlFields(control))
10
- if (value.trim())
11
- targets.push(target(`${outcome.key}.control.${control.key}.${field}`, "control", value, outcome.key));
10
+ for (const field of controlFieldFacts(control))
11
+ targets.push(target(`${outcome.key}.control.${control.key}.${field.claim_field}`, "control", field.statement, outcome.key));
12
+ if (outcome.product.control_relation_closure.state !== "unresolved")
13
+ targets.push(target(`${outcome.key}.control_relation_closure`, "control", outcome.product.control_relation_closure.statement, outcome.key));
14
+ for (const relation of outcome.product.control_relations)
15
+ targets.push(target(`${outcome.key}.control_relation.${relation.key}`, "control", relation.statement, outcome.key));
12
16
  for (const obligation of outcome.technical.obligations)
13
17
  targets.push(target(`${outcome.key}.obligation.${obligation.key}`, "technical_obligation", obligation.statement, outcome.key));
14
18
  for (const item of outcome.product.non_completing_outcomes)
@@ -57,32 +61,6 @@ function target(ref, kind, text, outcomeKey, scope) {
57
61
  ...(scope ? { scope } : {}),
58
62
  };
59
63
  }
60
- function controlFields(control) {
61
- return [
62
- ["surface", control.surface],
63
- ["region", control.region],
64
- ["location", control.location],
65
- ["control_type", control.control_type],
66
- ["label_content", control.label_content],
67
- ["user_task", control.user_task],
68
- ["visibility", control.visibility],
69
- ["availability", control.availability],
70
- ["trigger", control.trigger],
71
- ["input", control.input],
72
- ["validation", control.validation],
73
- ["default_value", control.default_value],
74
- ["interaction", control.interaction],
75
- ["navigation_result", control.navigation_result],
76
- ["loading", control.loading_state],
77
- ["empty", control.empty_state],
78
- ["success", control.success_state],
79
- ["failure", control.failure_state],
80
- ["recovery", control.recovery],
81
- ["permission", control.permission],
82
- ["feedback", control.feedback],
83
- ["accessibility", control.accessibility],
84
- ];
85
- }
86
64
  export function sourceKindForTarget(target) {
87
65
  return target.kind === "global_constraint"
88
66
  ? "technical_obligation"
@@ -19,6 +19,7 @@ export interface DeliveryStatusV2 {
19
19
  stages: Record<string, StageStatusV2>;
20
20
  ready_stages: string[];
21
21
  ready_outcomes: string[];
22
+ /** Compatibility alias for the advisory acceptance/verification frontier; never an implementation gate. */
22
23
  ready_for_implementation: string[];
23
24
  needs_reverify: string[];
24
25
  progress_passing: string[];