@rasensio/aidlc 1.16.0 → 1.17.0

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 (209) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +4 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/compliance.d.ts +36 -0
  6. package/dist/commands/compliance.d.ts.map +1 -0
  7. package/dist/commands/compliance.js +275 -0
  8. package/dist/commands/compliance.js.map +1 -0
  9. package/dist/commands/constitution.d.ts +26 -0
  10. package/dist/commands/constitution.d.ts.map +1 -0
  11. package/dist/commands/constitution.js +145 -0
  12. package/dist/commands/constitution.js.map +1 -0
  13. package/dist/commands/cost.d.ts.map +1 -1
  14. package/dist/commands/cost.js +54 -35
  15. package/dist/commands/cost.js.map +1 -1
  16. package/dist/commands/doctor.js +7 -0
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/gate.d.ts.map +1 -1
  19. package/dist/commands/gate.js +13 -0
  20. package/dist/commands/gate.js.map +1 -1
  21. package/dist/commands/init.d.ts.map +1 -1
  22. package/dist/commands/init.js +119 -10
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/metrics.d.ts.map +1 -1
  25. package/dist/commands/metrics.js +6 -1
  26. package/dist/commands/metrics.js.map +1 -1
  27. package/dist/commands/review.d.ts +2 -27
  28. package/dist/commands/review.d.ts.map +1 -1
  29. package/dist/commands/review.js +10 -166
  30. package/dist/commands/review.js.map +1 -1
  31. package/dist/commands/status.d.ts.map +1 -1
  32. package/dist/commands/status.js +7 -0
  33. package/dist/commands/status.js.map +1 -1
  34. package/dist/commands/transition.d.ts.map +1 -1
  35. package/dist/commands/transition.js +24 -1
  36. package/dist/commands/transition.js.map +1 -1
  37. package/dist/compile/adapters/claude-code.d.ts.map +1 -1
  38. package/dist/compile/adapters/claude-code.js +2 -1
  39. package/dist/compile/adapters/claude-code.js.map +1 -1
  40. package/dist/compile/adapters/codex.d.ts +22 -1
  41. package/dist/compile/adapters/codex.d.ts.map +1 -1
  42. package/dist/compile/adapters/codex.js +61 -27
  43. package/dist/compile/adapters/codex.js.map +1 -1
  44. package/dist/compile/adapters/cursor.d.ts.map +1 -1
  45. package/dist/compile/adapters/cursor.js +2 -19
  46. package/dist/compile/adapters/cursor.js.map +1 -1
  47. package/dist/compile/adapters/kiro.d.ts.map +1 -1
  48. package/dist/compile/adapters/kiro.js +1 -19
  49. package/dist/compile/adapters/kiro.js.map +1 -1
  50. package/dist/compile/adapters/windsurf.d.ts.map +1 -1
  51. package/dist/compile/adapters/windsurf.js +2 -1
  52. package/dist/compile/adapters/windsurf.js.map +1 -1
  53. package/dist/compile/guidance-install.d.ts +16 -19
  54. package/dist/compile/guidance-install.d.ts.map +1 -1
  55. package/dist/compile/guidance-install.js +26 -111
  56. package/dist/compile/guidance-install.js.map +1 -1
  57. package/dist/compile/layer-install.d.ts +122 -0
  58. package/dist/compile/layer-install.d.ts.map +1 -0
  59. package/dist/compile/layer-install.js +195 -0
  60. package/dist/compile/layer-install.js.map +1 -0
  61. package/dist/compile/loaders.d.ts +14 -0
  62. package/dist/compile/loaders.d.ts.map +1 -1
  63. package/dist/compile/loaders.js +23 -18
  64. package/dist/compile/loaders.js.map +1 -1
  65. package/dist/compile/skill-format.d.ts +39 -0
  66. package/dist/compile/skill-format.d.ts.map +1 -0
  67. package/dist/compile/skill-format.js +50 -0
  68. package/dist/compile/skill-format.js.map +1 -0
  69. package/dist/compliance/check.d.ts +79 -0
  70. package/dist/compliance/check.d.ts.map +1 -0
  71. package/dist/compliance/check.js +224 -0
  72. package/dist/compliance/check.js.map +1 -0
  73. package/dist/compliance/config.d.ts +91 -0
  74. package/dist/compliance/config.d.ts.map +1 -0
  75. package/dist/compliance/config.js +173 -0
  76. package/dist/compliance/config.js.map +1 -0
  77. package/dist/compliance/findings.d.ts +70 -0
  78. package/dist/compliance/findings.d.ts.map +1 -0
  79. package/dist/compliance/findings.js +132 -0
  80. package/dist/compliance/findings.js.map +1 -0
  81. package/dist/compliance/gate-criterion.d.ts +87 -0
  82. package/dist/compliance/gate-criterion.d.ts.map +1 -0
  83. package/dist/compliance/gate-criterion.js +168 -0
  84. package/dist/compliance/gate-criterion.js.map +1 -0
  85. package/dist/compliance/install.d.ts +135 -0
  86. package/dist/compliance/install.d.ts.map +1 -0
  87. package/dist/compliance/install.js +291 -0
  88. package/dist/compliance/install.js.map +1 -0
  89. package/dist/compliance/report.d.ts +144 -0
  90. package/dist/compliance/report.d.ts.map +1 -0
  91. package/dist/compliance/report.js +347 -0
  92. package/dist/compliance/report.js.map +1 -0
  93. package/dist/compliance/resolver.d.ts +65 -0
  94. package/dist/compliance/resolver.d.ts.map +1 -0
  95. package/dist/compliance/resolver.js +176 -0
  96. package/dist/compliance/resolver.js.map +1 -0
  97. package/dist/compliance/schema.d.ts +74 -0
  98. package/dist/compliance/schema.d.ts.map +1 -0
  99. package/dist/compliance/schema.js +474 -0
  100. package/dist/compliance/schema.js.map +1 -0
  101. package/dist/compliance/types.d.ts +140 -0
  102. package/dist/compliance/types.d.ts.map +1 -0
  103. package/dist/compliance/types.js +29 -0
  104. package/dist/compliance/types.js.map +1 -0
  105. package/dist/constitution/check.d.ts +22 -0
  106. package/dist/constitution/check.d.ts.map +1 -0
  107. package/dist/constitution/check.js +89 -0
  108. package/dist/constitution/check.js.map +1 -0
  109. package/dist/constitution/docx.d.ts +51 -0
  110. package/dist/constitution/docx.d.ts.map +1 -0
  111. package/dist/constitution/docx.js +157 -0
  112. package/dist/constitution/docx.js.map +1 -0
  113. package/dist/constitution/entries.d.ts +27 -0
  114. package/dist/constitution/entries.d.ts.map +1 -0
  115. package/dist/constitution/entries.js +65 -0
  116. package/dist/constitution/entries.js.map +1 -0
  117. package/dist/constitution/extract.d.ts +34 -0
  118. package/dist/constitution/extract.d.ts.map +1 -0
  119. package/dist/constitution/extract.js +117 -0
  120. package/dist/constitution/extract.js.map +1 -0
  121. package/dist/constitution/snapshot.d.ts +47 -0
  122. package/dist/constitution/snapshot.d.ts.map +1 -0
  123. package/dist/constitution/snapshot.js +159 -0
  124. package/dist/constitution/snapshot.js.map +1 -0
  125. package/dist/constitution/statements.d.ts +37 -0
  126. package/dist/constitution/statements.d.ts.map +1 -0
  127. package/dist/constitution/statements.js +64 -0
  128. package/dist/constitution/statements.js.map +1 -0
  129. package/dist/constitution/types.d.ts +80 -0
  130. package/dist/constitution/types.d.ts.map +1 -0
  131. package/dist/constitution/types.js +9 -0
  132. package/dist/constitution/types.js.map +1 -0
  133. package/dist/core/gate.d.ts +10 -0
  134. package/dist/core/gate.d.ts.map +1 -1
  135. package/dist/core/gate.js.map +1 -1
  136. package/dist/core/paths.d.ts +48 -0
  137. package/dist/core/paths.d.ts.map +1 -0
  138. package/dist/core/paths.js +57 -0
  139. package/dist/core/paths.js.map +1 -0
  140. package/dist/core/types.d.ts +12 -1
  141. package/dist/core/types.d.ts.map +1 -1
  142. package/dist/cost/hooks/registry.d.ts +44 -0
  143. package/dist/cost/hooks/registry.d.ts.map +1 -0
  144. package/dist/cost/hooks/registry.js +66 -0
  145. package/dist/cost/hooks/registry.js.map +1 -0
  146. package/dist/cost/pin.d.ts +63 -0
  147. package/dist/cost/pin.d.ts.map +1 -0
  148. package/dist/cost/pin.js +95 -0
  149. package/dist/cost/pin.js.map +1 -0
  150. package/dist/cost/providers/claude-code.d.ts +9 -2
  151. package/dist/cost/providers/claude-code.d.ts.map +1 -1
  152. package/dist/cost/providers/claude-code.js +10 -5
  153. package/dist/cost/providers/claude-code.js.map +1 -1
  154. package/dist/cost/providers/codex.d.ts +107 -0
  155. package/dist/cost/providers/codex.d.ts.map +1 -0
  156. package/dist/cost/providers/codex.js +283 -0
  157. package/dist/cost/providers/codex.js.map +1 -0
  158. package/dist/cost/providers/index.d.ts.map +1 -1
  159. package/dist/cost/providers/index.js +6 -0
  160. package/dist/cost/providers/index.js.map +1 -1
  161. package/dist/cost/rates.d.ts.map +1 -1
  162. package/dist/cost/rates.js +14 -0
  163. package/dist/cost/rates.js.map +1 -1
  164. package/dist/cost/report.d.ts +23 -1
  165. package/dist/cost/report.d.ts.map +1 -1
  166. package/dist/cost/report.js +26 -5
  167. package/dist/cost/report.js.map +1 -1
  168. package/dist/cost/sessions.d.ts +10 -1
  169. package/dist/cost/sessions.d.ts.map +1 -1
  170. package/dist/cost/sessions.js +10 -2
  171. package/dist/cost/sessions.js.map +1 -1
  172. package/dist/cost/sync.d.ts.map +1 -1
  173. package/dist/cost/sync.js +190 -5
  174. package/dist/cost/sync.js.map +1 -1
  175. package/dist/doctor/migrations/compliance-pack-drift.d.ts +62 -0
  176. package/dist/doctor/migrations/compliance-pack-drift.d.ts.map +1 -0
  177. package/dist/doctor/migrations/compliance-pack-drift.js +255 -0
  178. package/dist/doctor/migrations/compliance-pack-drift.js.map +1 -0
  179. package/dist/doctor/migrations/index.d.ts.map +1 -1
  180. package/dist/doctor/migrations/index.js +11 -0
  181. package/dist/doctor/migrations/index.js.map +1 -1
  182. package/dist/doctor/migrations/legacy-cost-hooks.d.ts.map +1 -1
  183. package/dist/doctor/migrations/legacy-cost-hooks.js +7 -4
  184. package/dist/doctor/migrations/legacy-cost-hooks.js.map +1 -1
  185. package/dist/doctor/migrations/missing-cost-shim.d.ts.map +1 -1
  186. package/dist/doctor/migrations/missing-cost-shim.js +5 -4
  187. package/dist/doctor/migrations/missing-cost-shim.js.map +1 -1
  188. package/dist/doctor/migrations/outdated-cost-hooks.d.ts.map +1 -1
  189. package/dist/doctor/migrations/outdated-cost-hooks.js +7 -4
  190. package/dist/doctor/migrations/outdated-cost-hooks.js.map +1 -1
  191. package/dist/doctor/runner.js +14 -0
  192. package/dist/doctor/runner.js.map +1 -1
  193. package/dist/doctor/types.d.ts +35 -3
  194. package/dist/doctor/types.d.ts.map +1 -1
  195. package/dist/menu/roster.d.ts.map +1 -1
  196. package/dist/menu/roster.js +5 -0
  197. package/dist/menu/roster.js.map +1 -1
  198. package/dist/metrics/report.d.ts.map +1 -1
  199. package/dist/metrics/report.js +11 -2
  200. package/dist/metrics/report.js.map +1 -1
  201. package/dist/review/findings-parser.d.ts +92 -0
  202. package/dist/review/findings-parser.d.ts.map +1 -0
  203. package/dist/review/findings-parser.js +196 -0
  204. package/dist/review/findings-parser.js.map +1 -0
  205. package/dist/setup/setup-compute.d.ts +10 -15
  206. package/dist/setup/setup-compute.d.ts.map +1 -1
  207. package/dist/setup/setup-compute.js +39 -0
  208. package/dist/setup/setup-compute.js.map +1 -1
  209. package/package.json +2 -2
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `aidlc compliance check` — assess a change against installed pack controls.
3
+ *
4
+ * **The central rule: absence of assessment is never a pass.**
5
+ *
6
+ * A control's `fails_when` is prose, pinned per control by a human, and no code
7
+ * evaluates prose. So controls declare `check_kind`: a `mechanical` control carries
8
+ * a `pattern` this module applies to the diff itself; an `agent` control can only be
9
+ * assessed in a review session, and its outcome is read from the compliance findings
10
+ * stream.
11
+ *
12
+ * The failure mode this design exists to prevent: if `check` merely read the
13
+ * agent-written stream, then in continuous integration — no agent, no
14
+ * `*.compliance.md` — it would find nothing wrong and exit 0 on every change. The
15
+ * exit-1 gate could never fire, and the surface a buyer pays for would be
16
+ * permanently, silently green. So an unassessed control is reported as
17
+ * `pending-agent-review`, and under `--enforce` that exits 2 rather than 0.
18
+ *
19
+ * `--enforce` is per-invocation and reads no config. `compliance.enforce` governs
20
+ * phase transitions only. One key for both would mean a team wanting a failing
21
+ * pull-request check had to also accept blocking every local phase transition.
22
+ *
23
+ * Exit codes:
24
+ * 0 nothing failing and nothing pending, or advisory mode
25
+ * 1 a failing critical control under `--enforce`
26
+ * 2 usage error, pack-load error, or an unassessed control under `--enforce`
27
+ *
28
+ * Requirements: compliance-packs/AC-35, AC-36, AC-79, AC-80, AC-86, AC-87,
29
+ * AC-102, AC-103
30
+ */
31
+ import type { Control } from './types.js';
32
+ /** What was established about one control for this change. */
33
+ export type ControlOutcome = {
34
+ control: string;
35
+ packId: string;
36
+ status: 'no-issues';
37
+ how: 'mechanical' | 'agent';
38
+ } | {
39
+ control: string;
40
+ packId: string;
41
+ status: 'failing';
42
+ how: 'mechanical' | 'agent';
43
+ severity: Control['severity'];
44
+ detail: string;
45
+ } | {
46
+ control: string;
47
+ packId: string;
48
+ status: 'pending-agent-review';
49
+ how: 'agent';
50
+ };
51
+ /** Result of a check run. */
52
+ export interface CheckResult {
53
+ /** One outcome per applicable control. */
54
+ controls: ControlOutcome[];
55
+ /** Whether `--enforce` was passed. */
56
+ enforce: boolean;
57
+ /** Non-fatal problems worth printing. */
58
+ warnings: string[];
59
+ /** Fatal problems: an installed pack that will not load. */
60
+ errors: string[];
61
+ /** The git ref the diff was taken against, or null for the working tree. */
62
+ diffRef: string | null;
63
+ exitCode: 0 | 1 | 2;
64
+ }
65
+ export interface CheckOptions {
66
+ /** Assess the diff against this git ref instead of the working tree. */
67
+ diffRef?: string;
68
+ /** Raise the exit code on a failing or unassessed control. */
69
+ enforce?: boolean;
70
+ }
71
+ /**
72
+ * Check a change against every installed pack's controls.
73
+ *
74
+ * Never throws. A pack that will not load is an error and exit 2, never an empty
75
+ * result — a check that reported nothing wrong because it could not read its own
76
+ * rules would be the worst possible outcome.
77
+ */
78
+ export declare function checkDiff(projectRoot: string, opts?: CheckOptions): CheckResult;
79
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/compliance/check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,8DAA8D;AAC9D,MAAM,MAAM,cAAc,GACtB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAA;CAAE,GACrF;IACE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,sBAAsB,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtF,6BAA6B;AAC7B,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAwFD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,WAAW,CAgHnF"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * `aidlc compliance check` — assess a change against installed pack controls.
3
+ *
4
+ * **The central rule: absence of assessment is never a pass.**
5
+ *
6
+ * A control's `fails_when` is prose, pinned per control by a human, and no code
7
+ * evaluates prose. So controls declare `check_kind`: a `mechanical` control carries
8
+ * a `pattern` this module applies to the diff itself; an `agent` control can only be
9
+ * assessed in a review session, and its outcome is read from the compliance findings
10
+ * stream.
11
+ *
12
+ * The failure mode this design exists to prevent: if `check` merely read the
13
+ * agent-written stream, then in continuous integration — no agent, no
14
+ * `*.compliance.md` — it would find nothing wrong and exit 0 on every change. The
15
+ * exit-1 gate could never fire, and the surface a buyer pays for would be
16
+ * permanently, silently green. So an unassessed control is reported as
17
+ * `pending-agent-review`, and under `--enforce` that exits 2 rather than 0.
18
+ *
19
+ * `--enforce` is per-invocation and reads no config. `compliance.enforce` governs
20
+ * phase transitions only. One key for both would mean a team wanting a failing
21
+ * pull-request check had to also accept blocking every local phase transition.
22
+ *
23
+ * Exit codes:
24
+ * 0 nothing failing and nothing pending, or advisory mode
25
+ * 1 a failing critical control under `--enforce`
26
+ * 2 usage error, pack-load error, or an unassessed control under `--enforce`
27
+ *
28
+ * Requirements: compliance-packs/AC-35, AC-36, AC-79, AC-80, AC-86, AC-87,
29
+ * AC-102, AC-103
30
+ */
31
+ import { execFileSync } from 'node:child_process';
32
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
33
+ import { join } from 'node:path';
34
+ import { readComplianceConfig } from './config.js';
35
+ import { parseComplianceFindings } from './findings.js';
36
+ import { resolvePack } from './resolver.js';
37
+ /**
38
+ * The diff text to assess.
39
+ *
40
+ * Working tree by default, so a developer can check before committing. A git failure
41
+ * yields null, which is reported rather than silently treated as an empty diff — an
42
+ * empty diff would make every mechanical control pass.
43
+ */
44
+ function readDiff(projectRoot, ref) {
45
+ const args = ref === null ? ['diff', '--unified=0'] : ['diff', '--unified=0', ref];
46
+ try {
47
+ return execFileSync('git', args, {
48
+ cwd: projectRoot,
49
+ encoding: 'utf8',
50
+ stdio: ['ignore', 'pipe', 'ignore'],
51
+ maxBuffer: 64 * 1024 * 1024,
52
+ });
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ /** Added lines of a unified diff, without the leading `+`. */
59
+ function addedLines(diff) {
60
+ return diff
61
+ .split('\n')
62
+ .filter((line) => line.startsWith('+') && !line.startsWith('+++'))
63
+ .map((line) => line.slice(1));
64
+ }
65
+ /**
66
+ * Agent-assessed outcomes, read from every compliance findings file in the project.
67
+ *
68
+ * Keyed by control id. A control cleared in one artifact and failing in another
69
+ * resolves to failing — the worse reading wins, because a check that reported the
70
+ * kinder of two answers would be worth nothing.
71
+ */
72
+ function agentOutcomes(projectRoot) {
73
+ const byControl = new Map();
74
+ const warnings = [];
75
+ const stateDir = join(projectRoot, '.aidlc', 'state');
76
+ if (!existsSync(stateDir))
77
+ return { byControl, warnings };
78
+ for (const instance of readdirSync(stateDir).sort()) {
79
+ const instanceDir = join(stateDir, instance);
80
+ let entries;
81
+ try {
82
+ entries = readdirSync(instanceDir);
83
+ }
84
+ catch {
85
+ continue;
86
+ }
87
+ for (const entry of entries.filter((f) => f.endsWith('.compliance.md'))) {
88
+ let file;
89
+ try {
90
+ file = parseComplianceFindings(readFileSync(join(instanceDir, entry), 'utf8'));
91
+ }
92
+ catch (err) {
93
+ warnings.push(`${instance}/${entry} is unreadable (${err.message})`);
94
+ continue;
95
+ }
96
+ warnings.push(...file.warnings.map((w) => `${instance}/${entry}: ${w}`));
97
+ for (const id of file.cleared) {
98
+ if (!byControl.has(id))
99
+ byControl.set(id, { status: 'no-issues' });
100
+ }
101
+ for (const finding of file.findings) {
102
+ if (finding.control === undefined)
103
+ continue;
104
+ if (finding.status !== 'open' || finding.placeholder === true)
105
+ continue;
106
+ // Failing always overrides a cleared reading from elsewhere.
107
+ byControl.set(finding.control, {
108
+ status: 'failing',
109
+ severity: finding.severity,
110
+ detail: finding.issue ?? finding.title,
111
+ });
112
+ }
113
+ }
114
+ }
115
+ return { byControl, warnings };
116
+ }
117
+ /**
118
+ * Check a change against every installed pack's controls.
119
+ *
120
+ * Never throws. A pack that will not load is an error and exit 2, never an empty
121
+ * result — a check that reported nothing wrong because it could not read its own
122
+ * rules would be the worst possible outcome.
123
+ */
124
+ export function checkDiff(projectRoot, opts = {}) {
125
+ const enforce = opts.enforce === true;
126
+ const diffRef = opts.diffRef ?? null;
127
+ const warnings = [];
128
+ const errors = [];
129
+ const config = readComplianceConfig(projectRoot);
130
+ if (config.packs.length === 0) {
131
+ return {
132
+ controls: [],
133
+ enforce,
134
+ warnings,
135
+ errors,
136
+ diffRef,
137
+ exitCode: 0,
138
+ };
139
+ }
140
+ const applicable = [];
141
+ for (const record of config.packs) {
142
+ const resolved = resolvePack(record.id);
143
+ if (!resolved.ok) {
144
+ errors.push(`installed pack '${record.id}' did not load: ${resolved.error.errors.join('; ')}`);
145
+ continue;
146
+ }
147
+ for (const control of resolved.pack.controls) {
148
+ applicable.push({ packId: record.id, control });
149
+ }
150
+ }
151
+ if (errors.length > 0) {
152
+ return { controls: [], enforce, warnings, errors, diffRef, exitCode: 2 };
153
+ }
154
+ const diff = readDiff(projectRoot, diffRef);
155
+ if (diff === null) {
156
+ // Not a git repository, or a bad ref. Mechanical controls cannot be assessed, and
157
+ // treating that as "nothing matched" would report a pass nobody earned.
158
+ warnings.push(diffRef === null
159
+ ? 'could not read a diff — mechanical controls cannot be assessed here'
160
+ : `could not read a diff against '${diffRef}' — mechanical controls cannot be assessed`);
161
+ }
162
+ const added = diff === null ? null : addedLines(diff);
163
+ const { byControl, warnings: agentWarnings } = agentOutcomes(projectRoot);
164
+ warnings.push(...agentWarnings);
165
+ const controls = [];
166
+ for (const { packId, control } of applicable) {
167
+ // An agent assessment always wins: a human looked at this control against this
168
+ // change, which is strictly better evidence than a pattern match.
169
+ const assessed = byControl.get(control.id);
170
+ if (assessed?.status === 'failing') {
171
+ controls.push({
172
+ control: control.id,
173
+ packId,
174
+ status: 'failing',
175
+ how: 'agent',
176
+ severity: assessed.severity ?? control.severity,
177
+ detail: assessed.detail ?? control.fails_when,
178
+ });
179
+ continue;
180
+ }
181
+ if (control.check_kind === 'mechanical' && control.pattern !== undefined) {
182
+ if (added === null) {
183
+ // No diff to match against. Not a pass.
184
+ controls.push({ control: control.id, packId, status: 'pending-agent-review', how: 'agent' });
185
+ continue;
186
+ }
187
+ const re = new RegExp(control.pattern);
188
+ const hit = added.find((line) => re.test(line));
189
+ if (hit !== undefined) {
190
+ controls.push({
191
+ control: control.id,
192
+ packId,
193
+ status: 'failing',
194
+ how: 'mechanical',
195
+ severity: control.severity,
196
+ detail: `${control.fails_when} — matched an added line`,
197
+ });
198
+ }
199
+ else {
200
+ controls.push({ control: control.id, packId, status: 'no-issues', how: 'mechanical' });
201
+ }
202
+ continue;
203
+ }
204
+ if (assessed?.status === 'no-issues') {
205
+ controls.push({ control: control.id, packId, status: 'no-issues', how: 'agent' });
206
+ continue;
207
+ }
208
+ controls.push({ control: control.id, packId, status: 'pending-agent-review', how: 'agent' });
209
+ }
210
+ const failingCritical = controls.some((c) => c.status === 'failing' && c.severity === 'critical');
211
+ const pending = controls.some((c) => c.status === 'pending-agent-review');
212
+ // Advisory mode reports everything and exits 0. Under enforcement, a failing
213
+ // critical control is 1 and an unassessed control is 2 — the continuous-integration
214
+ // case, where exiting 0 would be a silent false green.
215
+ let exitCode = 0;
216
+ if (enforce) {
217
+ if (failingCritical)
218
+ exitCode = 1;
219
+ else if (pending)
220
+ exitCode = 2;
221
+ }
222
+ return { controls, enforce, warnings, errors, diffRef, exitCode };
223
+ }
224
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/compliance/check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The `compliance:` section of `.aidlc/config.yaml`.
3
+ *
4
+ * Owns one section and nothing else. There is no central config loader in this
5
+ * codebase — ten call sites each join the path and parse it inline — and unifying
6
+ * them is out of scope here, so this module is deliberately narrow rather than an
7
+ * eleventh ad-hoc parse pretending to be general.
8
+ *
9
+ * The record shape exists to answer three questions no other state can:
10
+ *
11
+ * - **Is this layer locally edited?** By comparing the on-disk hash against the
12
+ * hash recorded *at install*. Comparing against the currently bundled body —
13
+ * what the framework path does, correctly, for its own layers — would make every
14
+ * installed layer read as edited the moment pack content is updated upstream, so
15
+ * `remove` would refuse to delete anything and corrected control text would never
16
+ * land.
17
+ * - **Which instances does a report cover?** By comparing each instance's
18
+ * completion moment against `installed_at`. A pack cannot evidence a change that
19
+ * predates it.
20
+ * - **Is an installed pack stale?** By comparing `version` against the resolvable
21
+ * one. Reported, never silently upgraded.
22
+ *
23
+ * Requirements: compliance-packs/AC-70, AC-71
24
+ */
25
+ import type { ModifiedDetector } from '../compile/layer-install.js';
26
+ /** One installed file, with the hash it had when it was written. */
27
+ export interface InstalledFile {
28
+ /** Filename within `.aidlc/guidance/`, as registered. */
29
+ path: string;
30
+ /** Hex sha256 of the bytes written at install. */
31
+ sha256: string;
32
+ }
33
+ /** One installed pack, as recorded in the project config. */
34
+ export interface InstalledPack {
35
+ id: string;
36
+ /** Pack content version at the time of install. */
37
+ version: string;
38
+ /** ISO 8601 UTC. Bounds the report's default scope. */
39
+ installed_at: string;
40
+ files: InstalledFile[];
41
+ }
42
+ /** The `compliance:` section. */
43
+ export interface ComplianceConfig {
44
+ /**
45
+ * Whether a critical control finding blocks a phase transition.
46
+ *
47
+ * Governs transitions only. The diff check takes `--enforce` per invocation, so
48
+ * a team can have a failing continuous-integration check without also blocking
49
+ * every local phase transition.
50
+ */
51
+ enforce: boolean;
52
+ packs: InstalledPack[];
53
+ }
54
+ /** The empty state: no packs, no enforcement. Distinct from "no section at all". */
55
+ export declare const EMPTY_COMPLIANCE: ComplianceConfig;
56
+ /** Hex sha256 of a string, as written to disk. */
57
+ export declare function hashContent(content: string): string;
58
+ /**
59
+ * Read the `compliance:` section.
60
+ *
61
+ * A malformed section reads as empty rather than throwing, because every consumer
62
+ * of this — the gate criterion, the report, the check — must degrade to "no packs
63
+ * installed" rather than break an unrelated command. A malformed section is
64
+ * reported by `aidlc doctor`, which is where a repair belongs.
65
+ */
66
+ export declare function readComplianceConfig(projectRoot: string): ComplianceConfig;
67
+ /**
68
+ * Write the `compliance:` section, leaving every other section untouched.
69
+ *
70
+ * The whole document is read, one key is replaced, and the document is written
71
+ * back — so a hand-added section survives, the same guarantee `computeSetup` gives
72
+ * for the sections it does not own.
73
+ */
74
+ export declare function writeComplianceConfig(projectRoot: string, config: ComplianceConfig): void;
75
+ /** The record for one installed pack, or null when it is not installed. */
76
+ export declare function findInstalledPack(config: ComplianceConfig, id: string): InstalledPack | null;
77
+ /**
78
+ * A modified-detector that compares against the hash recorded at install.
79
+ *
80
+ * This is the pack rule, and it is the reason `installLayers` takes the detector
81
+ * as a parameter rather than hard-coding one. `bundledBodyDiffers` answers "does
82
+ * this differ from what we ship *now*", which for a pack conflates "the user
83
+ * edited it" with "we published a new version".
84
+ *
85
+ * A file with no recorded hash counts as modified: we have no evidence we wrote
86
+ * it, so it is not ours to delete.
87
+ */
88
+ export declare function recordedHashDiffers(pack: InstalledPack | null): ModifiedDetector;
89
+ /** True when the pack is installed and its recorded version differs from `available`. */
90
+ export declare function isStale(pack: InstalledPack | null, available: string): boolean;
91
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/compliance/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAQH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,6DAA6D;AAC7D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,EAAE,gBAAgD,CAAC;AAEhF,kDAAkD;AAClD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAwCD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAW1E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAkBzF;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAE5F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,GAAG,gBAAgB,CAchF;AAeD,yFAAyF;AACzF,wBAAgB,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAE9E"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The `compliance:` section of `.aidlc/config.yaml`.
3
+ *
4
+ * Owns one section and nothing else. There is no central config loader in this
5
+ * codebase — ten call sites each join the path and parse it inline — and unifying
6
+ * them is out of scope here, so this module is deliberately narrow rather than an
7
+ * eleventh ad-hoc parse pretending to be general.
8
+ *
9
+ * The record shape exists to answer three questions no other state can:
10
+ *
11
+ * - **Is this layer locally edited?** By comparing the on-disk hash against the
12
+ * hash recorded *at install*. Comparing against the currently bundled body —
13
+ * what the framework path does, correctly, for its own layers — would make every
14
+ * installed layer read as edited the moment pack content is updated upstream, so
15
+ * `remove` would refuse to delete anything and corrected control text would never
16
+ * land.
17
+ * - **Which instances does a report cover?** By comparing each instance's
18
+ * completion moment against `installed_at`. A pack cannot evidence a change that
19
+ * predates it.
20
+ * - **Is an installed pack stale?** By comparing `version` against the resolvable
21
+ * one. Reported, never silently upgraded.
22
+ *
23
+ * Requirements: compliance-packs/AC-70, AC-71
24
+ */
25
+ import { createHash } from 'node:crypto';
26
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
27
+ import { join } from 'node:path';
28
+ import { parse as parseYaml } from 'yaml';
29
+ import { serializeYaml } from '../state/yaml-helpers.js';
30
+ /** The empty state: no packs, no enforcement. Distinct from "no section at all". */
31
+ export const EMPTY_COMPLIANCE = { enforce: false, packs: [] };
32
+ /** Hex sha256 of a string, as written to disk. */
33
+ export function hashContent(content) {
34
+ return createHash('sha256').update(content, 'utf8').digest('hex');
35
+ }
36
+ /** Absolute path of a project's config file. */
37
+ function configPath(projectRoot) {
38
+ return join(projectRoot, '.aidlc', 'config.yaml');
39
+ }
40
+ /** Read the whole config document, or an empty object when absent or unusable. */
41
+ function readConfigDocument(projectRoot) {
42
+ const path = configPath(projectRoot);
43
+ if (!existsSync(path))
44
+ return {};
45
+ try {
46
+ const parsed = parseYaml(readFileSync(path, 'utf8'));
47
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
48
+ return {};
49
+ return parsed;
50
+ }
51
+ catch {
52
+ return {};
53
+ }
54
+ }
55
+ /** Narrow an unknown value to an `InstalledFile`, or null. */
56
+ function toInstalledFile(raw) {
57
+ if (typeof raw !== 'object' || raw === null)
58
+ return null;
59
+ const r = raw;
60
+ if (typeof r.path !== 'string' || typeof r.sha256 !== 'string')
61
+ return null;
62
+ return { path: r.path, sha256: r.sha256 };
63
+ }
64
+ /** Narrow an unknown value to an `InstalledPack`, or null. */
65
+ function toInstalledPack(raw) {
66
+ if (typeof raw !== 'object' || raw === null)
67
+ return null;
68
+ const r = raw;
69
+ if (typeof r.id !== 'string' || typeof r.version !== 'string')
70
+ return null;
71
+ if (typeof r.installed_at !== 'string')
72
+ return null;
73
+ const files = Array.isArray(r.files)
74
+ ? r.files.map(toInstalledFile).filter((f) => f !== null)
75
+ : [];
76
+ return { id: r.id, version: r.version, installed_at: r.installed_at, files };
77
+ }
78
+ /**
79
+ * Read the `compliance:` section.
80
+ *
81
+ * A malformed section reads as empty rather than throwing, because every consumer
82
+ * of this — the gate criterion, the report, the check — must degrade to "no packs
83
+ * installed" rather than break an unrelated command. A malformed section is
84
+ * reported by `aidlc doctor`, which is where a repair belongs.
85
+ */
86
+ export function readComplianceConfig(projectRoot) {
87
+ const doc = readConfigDocument(projectRoot);
88
+ const section = doc.compliance;
89
+ if (typeof section !== 'object' || section === null || Array.isArray(section)) {
90
+ return { ...EMPTY_COMPLIANCE, packs: [] };
91
+ }
92
+ const s = section;
93
+ const packs = Array.isArray(s.packs)
94
+ ? s.packs.map(toInstalledPack).filter((p) => p !== null)
95
+ : [];
96
+ return { enforce: s.enforce === true, packs };
97
+ }
98
+ /**
99
+ * Write the `compliance:` section, leaving every other section untouched.
100
+ *
101
+ * The whole document is read, one key is replaced, and the document is written
102
+ * back — so a hand-added section survives, the same guarantee `computeSetup` gives
103
+ * for the sections it does not own.
104
+ */
105
+ export function writeComplianceConfig(projectRoot, config) {
106
+ const doc = readConfigDocument(projectRoot);
107
+ if (config.packs.length === 0 && !config.enforce) {
108
+ // No packs and no enforcement is indistinguishable from never having had a
109
+ // section, and an empty stanza invites the question of what it means.
110
+ delete doc.compliance;
111
+ }
112
+ else {
113
+ doc.compliance = {
114
+ enforce: config.enforce,
115
+ packs: config.packs.map((p) => ({
116
+ id: p.id,
117
+ version: p.version,
118
+ installed_at: p.installed_at,
119
+ files: p.files.map((f) => ({ path: f.path, sha256: f.sha256 })),
120
+ })),
121
+ };
122
+ }
123
+ writeFileSync(configPath(projectRoot), serializeYaml(doc), 'utf8');
124
+ }
125
+ /** The record for one installed pack, or null when it is not installed. */
126
+ export function findInstalledPack(config, id) {
127
+ return config.packs.find((p) => p.id === id) ?? null;
128
+ }
129
+ /**
130
+ * A modified-detector that compares against the hash recorded at install.
131
+ *
132
+ * This is the pack rule, and it is the reason `installLayers` takes the detector
133
+ * as a parameter rather than hard-coding one. `bundledBodyDiffers` answers "does
134
+ * this differ from what we ship *now*", which for a pack conflates "the user
135
+ * edited it" with "we published a new version".
136
+ *
137
+ * A file with no recorded hash counts as modified: we have no evidence we wrote
138
+ * it, so it is not ours to delete.
139
+ */
140
+ export function recordedHashDiffers(pack) {
141
+ const byPath = new Map((pack?.files ?? []).map((f) => [f.path, f.sha256]));
142
+ return (targetPath) => {
143
+ const recorded = byPath.get(basenameOf(targetPath));
144
+ if (recorded === undefined)
145
+ return true;
146
+ let actual;
147
+ try {
148
+ actual = hashContent(readFileSync(targetPath, 'utf8'));
149
+ }
150
+ catch {
151
+ // Unreadable now, so not safely ours to act on.
152
+ return true;
153
+ }
154
+ return actual !== recorded;
155
+ };
156
+ }
157
+ /**
158
+ * The registered filename of a target path.
159
+ *
160
+ * Recorded paths are the `file` values from a pack's index — relative names inside
161
+ * `.aidlc/guidance/` — while the detector receives an absolute path. Matching on
162
+ * the trailing segments keeps the two comparable without either side needing to
163
+ * know the project root.
164
+ */
165
+ function basenameOf(targetPath) {
166
+ const parts = targetPath.split(/[\\/]/);
167
+ return parts[parts.length - 1] ?? targetPath;
168
+ }
169
+ /** True when the pack is installed and its recorded version differs from `available`. */
170
+ export function isStale(pack, available) {
171
+ return pack !== null && pack.version !== available;
172
+ }
173
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/compliance/config.ts"],"names":[],"mappings":"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"}