@aperant/framework 0.11.0 → 0.12.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 (147) hide show
  1. package/CHANGELOG.md +213 -30
  2. package/agents/apt-planner.md +15 -0
  3. package/agents/apt-verifier.md +15 -10
  4. package/bin/apt-proof-video.mjs +256 -76
  5. package/dist/cli/commands/apply-recipe.d.mts +6 -0
  6. package/dist/cli/commands/apply-recipe.d.mts.map +1 -0
  7. package/dist/cli/commands/apply-recipe.mjs +98 -0
  8. package/dist/cli/commands/apply-recipe.mjs.map +1 -0
  9. package/dist/cli/commands/mobile-prepare.d.mts +53 -0
  10. package/dist/cli/commands/mobile-prepare.d.mts.map +1 -0
  11. package/dist/cli/commands/mobile-prepare.mjs +139 -0
  12. package/dist/cli/commands/mobile-prepare.mjs.map +1 -0
  13. package/dist/cli/commands/recipe-diff.d.mts +6 -0
  14. package/dist/cli/commands/recipe-diff.d.mts.map +1 -0
  15. package/dist/cli/commands/recipe-diff.mjs +75 -0
  16. package/dist/cli/commands/recipe-diff.mjs.map +1 -0
  17. package/dist/cli/commands/roadmap.d.mts.map +1 -1
  18. package/dist/cli/commands/roadmap.mjs +53 -0
  19. package/dist/cli/commands/roadmap.mjs.map +1 -1
  20. package/dist/cli/commands/validate-evidence.d.mts +1 -1
  21. package/dist/cli/commands/verify-sandbox.d.mts +6 -0
  22. package/dist/cli/commands/verify-sandbox.d.mts.map +1 -0
  23. package/dist/cli/commands/verify-sandbox.mjs +200 -0
  24. package/dist/cli/commands/verify-sandbox.mjs.map +1 -0
  25. package/dist/cli/consistency/parse-qa.d.mts.map +1 -1
  26. package/dist/cli/consistency/parse-qa.mjs +18 -4
  27. package/dist/cli/consistency/parse-qa.mjs.map +1 -1
  28. package/dist/cli/consistency/rules/r5-verdict-consistency.d.mts.map +1 -1
  29. package/dist/cli/consistency/rules/r5-verdict-consistency.mjs +11 -10
  30. package/dist/cli/consistency/rules/r5-verdict-consistency.mjs.map +1 -1
  31. package/dist/cli/coverage-check/user-outcomes.d.mts +7 -2
  32. package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -1
  33. package/dist/cli/coverage-check/user-outcomes.mjs +50 -17
  34. package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -1
  35. package/dist/cli/design/frontmatter-schema.d.mts +8 -8
  36. package/dist/cli/dispatch.d.mts.map +1 -1
  37. package/dist/cli/dispatch.mjs +28 -0
  38. package/dist/cli/dispatch.mjs.map +1 -1
  39. package/dist/cli/gate/gates/verify-approved.d.mts +0 -48
  40. package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
  41. package/dist/cli/gate/gates/verify-approved.mjs +23 -80
  42. package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
  43. package/dist/cli/install/mcp-server-specs.d.mts +6 -5
  44. package/dist/cli/install/mcp-server-specs.d.mts.map +1 -1
  45. package/dist/cli/install/mcp-server-specs.mjs +22 -4
  46. package/dist/cli/install/mcp-server-specs.mjs.map +1 -1
  47. package/dist/cli/personas/sidecar.d.mts +1 -1
  48. package/dist/cli/roadmap/backend-adapter.d.mts +43 -0
  49. package/dist/cli/roadmap/backend-adapter.d.mts.map +1 -0
  50. package/dist/cli/roadmap/backend-adapter.mjs +222 -0
  51. package/dist/cli/roadmap/backend-adapter.mjs.map +1 -0
  52. package/dist/cli/roadmap/lifecycle.d.mts +1 -1
  53. package/dist/cli/roadmap/rollup.d.mts +2 -2
  54. package/dist/cli/util/runtime-capabilities.d.mts +7 -0
  55. package/dist/cli/util/runtime-capabilities.d.mts.map +1 -1
  56. package/dist/cli/util/runtime-capabilities.mjs +65 -0
  57. package/dist/cli/util/runtime-capabilities.mjs.map +1 -1
  58. package/dist/cli/verify-proof/manifest-validator.mjs +13 -0
  59. package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -1
  60. package/dist/cli/verify-proof/resolver.d.mts +10 -7
  61. package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
  62. package/dist/cli/verify-proof/resolver.mjs +28 -7
  63. package/dist/cli/verify-proof/resolver.mjs.map +1 -1
  64. package/dist/cli/verify-proof/sandbox/apply.d.mts +64 -0
  65. package/dist/cli/verify-proof/sandbox/apply.d.mts.map +1 -0
  66. package/dist/cli/verify-proof/sandbox/apply.mjs +379 -0
  67. package/dist/cli/verify-proof/sandbox/apply.mjs.map +1 -0
  68. package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts +63 -0
  69. package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts.map +1 -0
  70. package/dist/cli/verify-proof/sandbox/gate-predicate.mjs +115 -0
  71. package/dist/cli/verify-proof/sandbox/gate-predicate.mjs.map +1 -0
  72. package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts +57 -0
  73. package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts.map +1 -0
  74. package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs +124 -0
  75. package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs.map +1 -0
  76. package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts +28 -0
  77. package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts.map +1 -0
  78. package/dist/cli/verify-proof/sandbox/recipe-diff.mjs +190 -0
  79. package/dist/cli/verify-proof/sandbox/recipe-diff.mjs.map +1 -0
  80. package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts +25 -0
  81. package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts.map +1 -0
  82. package/dist/cli/verify-proof/sandbox/sandbox-config.mjs +85 -0
  83. package/dist/cli/verify-proof/sandbox/sandbox-config.mjs.map +1 -0
  84. package/dist/cli/verify-proof/sandbox/self-test.d.mts +39 -0
  85. package/dist/cli/verify-proof/sandbox/self-test.d.mts.map +1 -0
  86. package/dist/cli/verify-proof/sandbox/self-test.mjs +248 -0
  87. package/dist/cli/verify-proof/sandbox/self-test.mjs.map +1 -0
  88. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  89. package/dist/plugin/agents/apt-planner.md +15 -0
  90. package/dist/plugin/agents/apt-verifier.md +15 -10
  91. package/dist/plugin/skills/apt-plan/SKILL.md +23 -0
  92. package/dist/plugin/skills/apt-run/SKILL.md +1 -1
  93. package/dist/plugin/skills/apt-setup/SKILL.md +56 -0
  94. package/dist/plugin/skills/apt-verify/SKILL.md +20 -15
  95. package/dist/plugin/skills/apt-verify-proof/SKILL.md +146 -11
  96. package/dist/proof-report.d.ts.map +1 -1
  97. package/dist/proof-report.js +4 -1
  98. package/dist/proof-report.js.map +1 -1
  99. package/dist/types/config.d.ts +42 -4
  100. package/dist/types/config.d.ts.map +1 -1
  101. package/dist/types/qa-scoring.d.ts +45 -9
  102. package/dist/types/qa-scoring.d.ts.map +1 -1
  103. package/dist/types/qa-scoring.js +34 -13
  104. package/dist/types/qa-scoring.js.map +1 -1
  105. package/dist/types/state.d.ts +7 -2
  106. package/dist/types/state.d.ts.map +1 -1
  107. package/dist/types/task-record.d.ts +10 -2
  108. package/dist/types/task-record.d.ts.map +1 -1
  109. package/drivers/mobile/README.md +40 -0
  110. package/drivers/mobile/driver.mjs +106 -0
  111. package/drivers/mobile/manifest.json +49 -0
  112. package/package.json +138 -138
  113. package/prompts/inbox-clarification.md +11 -0
  114. package/prompts/inbox-triage.md +20 -0
  115. package/prompts/qa_orchestrator_agentic.md +18 -17
  116. package/prompts/qa_reviewer.md +17 -15
  117. package/skills/apt-plan/SKILL.md +23 -0
  118. package/skills/apt-run/SKILL.md +1 -1
  119. package/skills/apt-setup/SKILL.md +56 -0
  120. package/skills/apt-verifier.md +7 -4
  121. package/skills/apt-verify/SKILL.md +20 -15
  122. package/skills/apt-verify-proof/SKILL.md +146 -11
  123. package/src/cli/commands/apply-recipe.mjs +105 -0
  124. package/src/cli/commands/mobile-prepare.mjs +151 -0
  125. package/src/cli/commands/recipe-diff.mjs +81 -0
  126. package/src/cli/commands/roadmap.mjs +54 -0
  127. package/src/cli/commands/verify-sandbox.mjs +231 -0
  128. package/src/cli/consistency/parse-qa.mjs +20 -4
  129. package/src/cli/consistency/rules/r5-verdict-consistency.mjs +11 -13
  130. package/src/cli/coverage-check/user-outcomes.mjs +52 -17
  131. package/src/cli/dispatch.mjs +27 -0
  132. package/src/cli/gate/gates/verify-approved.mjs +22 -81
  133. package/src/cli/install/mcp-server-specs.mjs +24 -4
  134. package/src/cli/roadmap/backend-adapter.mjs +231 -0
  135. package/src/cli/util/runtime-capabilities.mjs +67 -0
  136. package/src/cli/verify-proof/manifest-validator.mjs +15 -0
  137. package/src/cli/verify-proof/resolver.mjs +27 -7
  138. package/src/cli/verify-proof/sandbox/apply.mjs +401 -0
  139. package/src/cli/verify-proof/sandbox/gate-predicate.mjs +126 -0
  140. package/src/cli/verify-proof/sandbox/pattern-matcher.mjs +127 -0
  141. package/src/cli/verify-proof/sandbox/recipe-diff.mjs +208 -0
  142. package/src/cli/verify-proof/sandbox/sandbox-config.mjs +82 -0
  143. package/src/cli/verify-proof/sandbox/sandbox-schema.json +33 -0
  144. package/src/cli/verify-proof/sandbox/self-test.mjs +265 -0
  145. package/templates/config.json +10 -1
  146. package/templates/proof-verification.md +27 -8
  147. package/workflows/verify-proof.md +376 -302
@@ -0,0 +1,115 @@
1
+ /**
2
+ * gate-predicate.mjs — extracted decision function for the sandbox
3
+ * precondition gate at apt-verify-proof/SKILL.md Step 2c.
4
+ *
5
+ * Refactored out of the SKILL.md prose so the e2e test (subtask 7.3)
6
+ * can call it directly without re-implementing the dispatch table.
7
+ *
8
+ * Inputs:
9
+ * - config: {verification?: {sandbox?: {enabled:bool, ...}}}
10
+ * - stateFile: the v2-shaped envelope read from
11
+ * .aperant/state/verify-proof-sandbox-verified.json
12
+ * OR null when the file is missing.
13
+ * - parallelRequested: bool — true when /apt:verify-proof was invoked
14
+ * with --parallel.
15
+ *
16
+ * Returns:
17
+ * - {action: 'proceed'} — verified + approved, walker may run
18
+ * - {action: 'skip-gate'} — --parallel NOT requested OR
19
+ * verification.sandbox absent;
20
+ * Fast Path Guarantee (ID-07).
21
+ * - {action: 'exit', envelope} — verified:false OR state missing;
22
+ * envelope is the v2 fail-loud shape.
23
+ *
24
+ * The `envelope` shape on exit is:
25
+ * {status:'error', code:'E_SANDBOX_UNVERIFIED', verdict:'needs_human',
26
+ * reason:'sandbox_unverified', remediation:'/apt:verify-proof setup'}
27
+ *
28
+ * No I/O, no process.exit — pure function for testability.
29
+ */
30
+ const FAIL_ENVELOPE = Object.freeze({
31
+ status: 'error',
32
+ code: 'E_SANDBOX_UNVERIFIED',
33
+ verdict: 'needs_human',
34
+ reason: 'sandbox_unverified',
35
+ remediation: '/apt:verify-proof setup',
36
+ });
37
+ const STALE_VERDICT_ENVELOPE = Object.freeze({
38
+ status: 'error',
39
+ code: 'E_SANDBOX_VERDICT_STALE',
40
+ verdict: 'needs_human',
41
+ reason: 'sandbox_recipe_version_drift',
42
+ remediation: 'apt-tools verify-sandbox <project-dir>',
43
+ });
44
+ /**
45
+ * @typedef {Object} GateInput
46
+ * @property {object|null|undefined} config
47
+ * @property {object|null|undefined} stateFile
48
+ * @property {boolean} parallelRequested
49
+ */
50
+ /**
51
+ * @typedef {Object} GateEnvelope
52
+ * @property {'error'} status
53
+ * @property {string} code
54
+ * @property {string} verdict
55
+ * @property {string} reason
56
+ * @property {string} remediation
57
+ */
58
+ /**
59
+ * @typedef {Object} GateOutput
60
+ * @property {'proceed'|'skip-gate'|'exit'} action
61
+ * @property {GateEnvelope} [envelope]
62
+ */
63
+ /**
64
+ * @param {GateInput} input
65
+ * @returns {GateOutput}
66
+ */
67
+ export function gatePredicate({ config, stateFile, parallelRequested }) {
68
+ // (1) --parallel NOT requested → gate is a no-op (Fast Path Guarantee ID-07).
69
+ if (!parallelRequested)
70
+ return { action: 'skip-gate' };
71
+ // (2) verification.sandbox is ABSENT → also skip-gate (the adopter
72
+ // has not opted in; respect the absent-config branch as no-op).
73
+ const sandboxCfg = config?.verification?.sandbox;
74
+ if (!sandboxCfg || sandboxCfg.enabled !== true) {
75
+ // Note: even though --parallel was requested, the absent-config
76
+ // branch returns skip-gate; the SKILL.md Step 2c prose handles
77
+ // the absent-config case via the first-run hook (Step 1) which
78
+ // is a UX surface, not a gate failure.
79
+ return { action: 'skip-gate' };
80
+ }
81
+ // (3) state file present + verified:true + verdict:'approved' → check version drift.
82
+ if (stateFile && stateFile.verified === true && stateFile.verdict === 'approved') {
83
+ // (3a) Version drift check: when both sides have recipeVersion, verify the
84
+ // state file's version is in the active recipe's compatibleWithApplied[].
85
+ // If either side has null/undefined recipeVersion, fall through (compat
86
+ // with pre-LOG-003 state files that predate recipeVersion population).
87
+ const stateVersion = stateFile.recipeVersion ?? null;
88
+ const activeVersion = sandboxCfg.recipeVersion ?? null;
89
+ const compatibleWith = Array.isArray(sandboxCfg.compatibleWithApplied)
90
+ ? sandboxCfg.compatibleWithApplied
91
+ : null;
92
+ if (stateVersion && activeVersion && compatibleWith !== null) {
93
+ // Both sides versioned — only stale if the state version is NOT
94
+ // listed in the active recipe's compatibleWithApplied AND the
95
+ // versions differ.
96
+ if (stateVersion !== activeVersion && !compatibleWith.includes(stateVersion)) {
97
+ return { action: 'exit', envelope: { ...STALE_VERDICT_ENVELOPE } };
98
+ }
99
+ }
100
+ return { action: 'proceed' };
101
+ }
102
+ // (4) Everything else → fail loud with the v2 envelope.
103
+ return { action: 'exit', envelope: { ...FAIL_ENVELOPE } };
104
+ }
105
+ /**
106
+ * The frozen fail envelope — exported so callers can detect-and-match
107
+ * without re-stringifying.
108
+ */
109
+ export const SANDBOX_UNVERIFIED_ENVELOPE = FAIL_ENVELOPE;
110
+ /**
111
+ * The frozen stale-verdict envelope — emitted when the state file's
112
+ * recipeVersion is no longer in the active recipe's compatibleWithApplied[].
113
+ */
114
+ export const SANDBOX_VERDICT_STALE_ENVELOPE = STALE_VERDICT_ENVELOPE;
115
+ //# sourceMappingURL=gate-predicate.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-predicate.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/gate-predicate.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,MAAM,EAAE,OAAO;IACf,IAAI,EAAE,sBAAsB;IAC5B,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,oBAAoB;IAC5B,WAAW,EAAE,yBAAyB;CACtC,CAAC,CAAA;AAEF,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,OAAO;IACf,IAAI,EAAE,yBAAyB;IAC/B,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,8BAA8B;IACtC,WAAW,EAAE,wCAAwC;CACrD,CAAC,CAAA;AAEF;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAAE;IACrE,8EAA8E;IAC9E,IAAI,CAAC,iBAAiB;QAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAEtD,mEAAmE;IACnE,oEAAoE;IACpE,MAAM,UAAU,GAAG,MAAM,EAAE,YAAY,EAAE,OAAO,CAAA;IAChD,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAChD,gEAAgE;QAChE,+DAA+D;QAC/D,+DAA+D;QAC/D,uCAAuC;QACvC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAC/B,CAAC;IAED,qFAAqF;IACrF,IAAI,SAAS,IAAI,SAAS,CAAC,QAAQ,KAAK,IAAI,IAAI,SAAS,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QAClF,2EAA2E;QAC3E,+EAA+E;QAC/E,6EAA6E;QAC7E,4EAA4E;QAC5E,MAAM,YAAY,GAAG,SAAS,CAAC,aAAa,IAAI,IAAI,CAAA;QACpD,MAAM,aAAa,GAAG,UAAU,CAAC,aAAa,IAAI,IAAI,CAAA;QACtD,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,qBAAqB,CAAC;YACrE,CAAC,CAAC,UAAU,CAAC,qBAAqB;YAClC,CAAC,CAAC,IAAI,CAAA;QAEP,IAAI,YAAY,IAAI,aAAa,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC9D,gEAAgE;YAChE,8DAA8D;YAC9D,mBAAmB;YACnB,IAAI,YAAY,KAAK,aAAa,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,GAAG,sBAAsB,EAAE,EAAE,CAAA;YACnE,CAAC;QACF,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IAC7B,CAAC;IAED,wDAAwD;IACxD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,GAAG,aAAa,EAAE,EAAE,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,aAAa,CAAA;AAExD;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,sBAAsB,CAAA"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @typedef {import('../../util/runtime-capabilities.mjs').RuntimeCapabilities} RuntimeCapabilities
3
+ */
4
+ /**
5
+ * @typedef {Object} SandboxRecipe
6
+ * @property {string} recipeId
7
+ * @property {string} recipeVersion
8
+ * @property {string} name
9
+ * @property {string} description
10
+ * @property {{capabilities: string[], deps: string[]}} match
11
+ * @property {{logPattern: string, timeoutMs: number}} readiness
12
+ * @property {Array<object>} changes
13
+ * @property {string[]} compatibleWithApplied
14
+ */
15
+ /**
16
+ * @typedef {Object} MatchResult
17
+ * @property {string|null} recipeId
18
+ * @property {string} reason
19
+ */
20
+ /**
21
+ * @param {RuntimeCapabilities} runtimeCaps
22
+ * @param {Record<string, unknown> | null} packageJson
23
+ * @param {SandboxRecipe[]} recipes
24
+ * @returns {MatchResult}
25
+ */
26
+ export function matchPattern(runtimeCaps: RuntimeCapabilities, packageJson: Record<string, unknown> | null, recipes: SandboxRecipe[]): MatchResult;
27
+ /**
28
+ * Convenience overload — load the project's package.json from disk.
29
+ *
30
+ * @param {string} projectDir
31
+ * @param {RuntimeCapabilities} runtimeCaps
32
+ * @param {SandboxRecipe[]} recipes
33
+ * @returns {MatchResult}
34
+ */
35
+ export function matchPatternForProject(projectDir: string, runtimeCaps: RuntimeCapabilities, recipes: SandboxRecipe[]): MatchResult;
36
+ export type RuntimeCapabilities = import("../../util/runtime-capabilities.mjs").RuntimeCapabilities;
37
+ export type SandboxRecipe = {
38
+ recipeId: string;
39
+ recipeVersion: string;
40
+ name: string;
41
+ description: string;
42
+ match: {
43
+ capabilities: string[];
44
+ deps: string[];
45
+ };
46
+ readiness: {
47
+ logPattern: string;
48
+ timeoutMs: number;
49
+ };
50
+ changes: Array<object>;
51
+ compatibleWithApplied: string[];
52
+ };
53
+ export type MatchResult = {
54
+ recipeId: string | null;
55
+ reason: string;
56
+ };
57
+ //# sourceMappingURL=pattern-matcher.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-matcher.d.mts","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/pattern-matcher.mjs"],"names":[],"mappings":"AAuBA;;GAEG;AAEH;;;;;;;;;;GAUG;AAEH;;;;GAIG;AAEH;;;;;GAKG;AACH,0CALW,mBAAmB,eACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,WAC9B,aAAa,EAAE,GACb,WAAW,CA4BvB;AAED;;;;;;;GAOG;AACH,mDALW,MAAM,eACN,mBAAmB,WACnB,aAAa,EAAE,GACb,WAAW,CAavB;kCA1EY,OAAO,qCAAqC,EAAE,mBAAmB;;cAKhE,MAAM;mBACN,MAAM;UACN,MAAM;iBACN,MAAM;WACN;QAAC,YAAY,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAC;eACxC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC;aACvC,KAAK,CAAC,MAAM,CAAC;2BACb,MAAM,EAAE;;;cAKR,MAAM,GAAC,IAAI;YACX,MAAM"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * pattern-matcher.mjs — sandbox-recipe pattern matcher.
3
+ *
4
+ * Walks the registered recipes' `match.capabilities[]` + `match.deps[]`
5
+ * predicates against the loaded runtime capabilities + package.json deps,
6
+ * and returns the FIRST matching recipe (or null with a human reason).
7
+ *
8
+ * Two-arg shape:
9
+ * - `match.capabilities[]` — every entry must be `true` on the
10
+ * RuntimeCapabilities record (AND semantics — all required).
11
+ * - `match.deps[]` — at least one entry must appear in the merged
12
+ * dependencies + devDependencies of the project's root package.json
13
+ * (OR semantics — any-of).
14
+ *
15
+ * This split lets a recipe say "I need is_electron AND one of
16
+ * [@libsql/client, better-sqlite3]" without enumerating every combination.
17
+ *
18
+ * See: spec ID-04 (v1 ships ONE recipe — electron-libsql), AC5.
19
+ */
20
+ import { existsSync, readFileSync } from 'node:fs';
21
+ import { join, resolve } from 'node:path';
22
+ /**
23
+ * @typedef {import('../../util/runtime-capabilities.mjs').RuntimeCapabilities} RuntimeCapabilities
24
+ */
25
+ /**
26
+ * @typedef {Object} SandboxRecipe
27
+ * @property {string} recipeId
28
+ * @property {string} recipeVersion
29
+ * @property {string} name
30
+ * @property {string} description
31
+ * @property {{capabilities: string[], deps: string[]}} match
32
+ * @property {{logPattern: string, timeoutMs: number}} readiness
33
+ * @property {Array<object>} changes
34
+ * @property {string[]} compatibleWithApplied
35
+ */
36
+ /**
37
+ * @typedef {Object} MatchResult
38
+ * @property {string|null} recipeId
39
+ * @property {string} reason
40
+ */
41
+ /**
42
+ * @param {RuntimeCapabilities} runtimeCaps
43
+ * @param {Record<string, unknown> | null} packageJson
44
+ * @param {SandboxRecipe[]} recipes
45
+ * @returns {MatchResult}
46
+ */
47
+ export function matchPattern(runtimeCaps, packageJson, recipes) {
48
+ if (!Array.isArray(recipes) || recipes.length === 0) {
49
+ return { recipeId: null, reason: 'no recipes registered' };
50
+ }
51
+ const deps = mergedDeps(packageJson);
52
+ for (const recipe of recipes) {
53
+ const reqCaps = recipe.match?.capabilities ?? [];
54
+ const anyDeps = recipe.match?.deps ?? [];
55
+ const capsOk = reqCaps.every((cap) => runtimeCaps && runtimeCaps[cap] === true);
56
+ if (!capsOk)
57
+ continue;
58
+ // deps[] is any-of — recipe matches if at least one declared dep is in
59
+ // the project. An empty deps[] means "no dep constraint" (cap-only match).
60
+ const depsOk = anyDeps.length === 0 || anyDeps.some((d) => deps.has(d.toLowerCase()));
61
+ if (!depsOk)
62
+ continue;
63
+ return { recipeId: recipe.recipeId, reason: 'matched' };
64
+ }
65
+ return {
66
+ recipeId: null,
67
+ reason: `no recipe available for runtime: ${describeRuntime(runtimeCaps)}`,
68
+ };
69
+ }
70
+ /**
71
+ * Convenience overload — load the project's package.json from disk.
72
+ *
73
+ * @param {string} projectDir
74
+ * @param {RuntimeCapabilities} runtimeCaps
75
+ * @param {SandboxRecipe[]} recipes
76
+ * @returns {MatchResult}
77
+ */
78
+ export function matchPatternForProject(projectDir, runtimeCaps, recipes) {
79
+ const pkgPath = join(resolve(projectDir), 'package.json');
80
+ let pkg = null;
81
+ if (existsSync(pkgPath)) {
82
+ try {
83
+ pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
84
+ }
85
+ catch {
86
+ /* fall through with pkg=null */
87
+ }
88
+ }
89
+ return matchPattern(runtimeCaps, pkg, recipes);
90
+ }
91
+ /**
92
+ * Build a Set of lowercased package names from dependencies + devDependencies.
93
+ *
94
+ * @param {Record<string, unknown> | null} packageJson
95
+ */
96
+ function mergedDeps(packageJson) {
97
+ const out = new Set();
98
+ if (packageJson === null || typeof packageJson !== 'object')
99
+ return out;
100
+ const d = /** @type {Record<string, unknown>} */ (packageJson.dependencies);
101
+ const dd = /** @type {Record<string, unknown>} */ (packageJson.devDependencies);
102
+ if (d && typeof d === 'object')
103
+ for (const k of Object.keys(d))
104
+ out.add(k.toLowerCase());
105
+ if (dd && typeof dd === 'object')
106
+ for (const k of Object.keys(dd))
107
+ out.add(k.toLowerCase());
108
+ return out;
109
+ }
110
+ /**
111
+ * One-line description of the detected runtime shape — used in the
112
+ * no-match reason so an adopter sees what the matcher saw.
113
+ *
114
+ * @param {RuntimeCapabilities} runtimeCaps
115
+ */
116
+ function describeRuntime(runtimeCaps) {
117
+ if (!runtimeCaps || typeof runtimeCaps !== 'object')
118
+ return 'unknown';
119
+ const active = Object.keys(runtimeCaps).filter((k) => runtimeCaps[k] === true);
120
+ if (active.length === 0)
121
+ return 'no capabilities detected';
122
+ return active.join(', ');
123
+ }
124
+ //# sourceMappingURL=pattern-matcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-matcher.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/pattern-matcher.mjs"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @param {{projectDir: string, recipeId: string, recipes: object[]}} args
3
+ * @returns {{
4
+ * format: 'markdown',
5
+ * body: string,
6
+ * changes: Array<{path: string, kind: string, summary: string}>,
7
+ * envelope: {status: 'ok'|'error', recipe_version: string|null, compatible: boolean}
8
+ * }}
9
+ */
10
+ export function generateRecipeDiff({ projectDir, recipeId, recipes }: {
11
+ projectDir: string;
12
+ recipeId: string;
13
+ recipes: object[];
14
+ }): {
15
+ format: "markdown";
16
+ body: string;
17
+ changes: Array<{
18
+ path: string;
19
+ kind: string;
20
+ summary: string;
21
+ }>;
22
+ envelope: {
23
+ status: "ok" | "error";
24
+ recipe_version: string | null;
25
+ compatible: boolean;
26
+ };
27
+ };
28
+ //# sourceMappingURL=recipe-diff.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-diff.d.mts","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/recipe-diff.mjs"],"names":[],"mappings":"AA8CA;;;;;;;;GAQG;AACH,sEARW;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAC,GACvD;IACR,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IAC9D,QAAQ,EAAE;QAAC,MAAM,EAAE,IAAI,GAAC,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,GAAC,IAAI,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAC,CAAA;CACnF,CAuDH"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * recipe-diff.mjs — read-only Markdown diff generator for a recipe.
3
+ *
4
+ * Loads the recipe, walks `changes[]`, and emits a Markdown report
5
+ * enumerating every proposed write WITH `--- before` / `+++ after`
6
+ * blocks. The generator is pure read — it never writes to the adopter's
7
+ * tree (ID-02). The companion `apply-recipe` command is the only
8
+ * writer.
9
+ *
10
+ * Public contract:
11
+ * generateRecipeDiff({projectDir, recipeId, recipes}) → {
12
+ * format: 'markdown',
13
+ * body: string,
14
+ * changes: [{path, kind, summary}],
15
+ * envelope: {status, recipe_version, compatible}
16
+ * }
17
+ *
18
+ * The Markdown is user-facing and load-bearing — drift = golden-snapshot
19
+ * test failure by design (subtask 2.1 / TD-03).
20
+ */
21
+ import { existsSync, readFileSync } from 'node:fs';
22
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+ const __dirname = dirname(fileURLToPath(import.meta.url));
25
+ /**
26
+ * Defense-in-depth path-under-root check. Returns the validated absolute
27
+ * path or null when the candidate escapes the root. The diff generator
28
+ * surfaces a "template not found" envelope rather than throwing (read-
29
+ * only generator stays non-fatal).
30
+ *
31
+ * @param {string} rootAbs
32
+ * @param {string} relPath
33
+ * @returns {string|null}
34
+ */
35
+ function safeJoinUnderRoot(rootAbs, relPath) {
36
+ if (typeof relPath !== 'string' || relPath.length === 0)
37
+ return null;
38
+ if (isAbsolute(relPath))
39
+ return null;
40
+ const candidate = resolve(rootAbs, relPath);
41
+ const rel = relative(rootAbs, candidate);
42
+ if (rel.startsWith('..') || isAbsolute(rel))
43
+ return null;
44
+ return candidate;
45
+ }
46
+ /**
47
+ * @param {{projectDir: string, recipeId: string, recipes: object[]}} args
48
+ * @returns {{
49
+ * format: 'markdown',
50
+ * body: string,
51
+ * changes: Array<{path: string, kind: string, summary: string}>,
52
+ * envelope: {status: 'ok'|'error', recipe_version: string|null, compatible: boolean}
53
+ * }}
54
+ */
55
+ export function generateRecipeDiff({ projectDir, recipeId, recipes }) {
56
+ const recipe = (recipes ?? []).find((r) => r && r.recipeId === recipeId);
57
+ if (!recipe) {
58
+ return {
59
+ format: 'markdown',
60
+ body: `# No recipe available\n\nrecipeId \`${recipeId}\` is not in the registered recipes list.\n`,
61
+ changes: [],
62
+ envelope: { status: 'error', recipe_version: null, compatible: false },
63
+ };
64
+ }
65
+ const adopterRoot = resolve(projectDir);
66
+ const changeRows = [];
67
+ const sections = [];
68
+ for (const change of recipe.changes ?? []) {
69
+ // Defense-in-depth: a malicious recipe could otherwise read
70
+ // arbitrary host files via `change.path: '../../etc/passwd'`. The
71
+ // diff generator is read-only but the read CONTENT lands in the
72
+ // emitted Markdown — refuse to enumerate paths outside root.
73
+ const targetAbs = safeJoinUnderRoot(adopterRoot, change.path);
74
+ if (targetAbs === null) {
75
+ sections.push(`## ${change.path}\n\n_(rejected: change.path escapes project root — recipe defect)_\n`);
76
+ changeRows.push({
77
+ path: change.path,
78
+ kind: change.kind,
79
+ summary: 'rejected (path escapes root)',
80
+ });
81
+ continue;
82
+ }
83
+ const exists = existsSync(targetAbs);
84
+ const before = exists ? safeReadFile(targetAbs) : '';
85
+ const after = renderAfter(change, recipe, adopterRoot);
86
+ const summary = renderChangeSummary(change, exists);
87
+ changeRows.push({ path: change.path, kind: change.kind, summary });
88
+ sections.push(renderChangeSection(change, before, after, exists));
89
+ }
90
+ const body = renderBody(recipe, sections);
91
+ return {
92
+ format: 'markdown',
93
+ body,
94
+ changes: changeRows,
95
+ envelope: {
96
+ status: 'ok',
97
+ recipe_version: recipe.recipeVersion ?? null,
98
+ compatible: true,
99
+ },
100
+ };
101
+ }
102
+ function renderBody(recipe, sections) {
103
+ const lines = [];
104
+ lines.push(`# Recipe diff: ${recipe.name ?? recipe.recipeId}`);
105
+ lines.push('');
106
+ lines.push(`**Recipe:** \`${recipe.recipeId}\` v${recipe.recipeVersion} ` +
107
+ `(compatibleWithApplied: ${JSON.stringify(recipe.compatibleWithApplied ?? [])})`);
108
+ lines.push('');
109
+ lines.push(recipe.description ?? '');
110
+ lines.push('');
111
+ lines.push(`This recipe proposes **${(recipe.changes ?? []).length}** changes.`);
112
+ lines.push('No file is written until you run `apt-tools apply-recipe . --confirm` ' +
113
+ 'and accept each change individually.');
114
+ lines.push('');
115
+ for (const section of sections) {
116
+ lines.push(section);
117
+ lines.push('');
118
+ }
119
+ return lines.join('\n').replace(/\n{3,}/g, '\n\n');
120
+ }
121
+ function renderChangeSummary(change, exists) {
122
+ const verb = change.kind === 'file-create' ? 'create' : change.kind === 'json-merge' ? 'merge into' : 'edit';
123
+ const target = exists ? change.path : `${change.path} (new file)`;
124
+ return `${verb} ${target}`;
125
+ }
126
+ function renderChangeSection(change, before, after, exists) {
127
+ const lines = [];
128
+ lines.push(`## ${change.path}`);
129
+ lines.push('');
130
+ lines.push(`- kind: \`${change.kind}\``, `- marker: \`${change.marker ?? '(none)'}\``, `- target ${exists ? 'exists' : 'does not exist'} on disk`);
131
+ lines.push('');
132
+ lines.push('```diff');
133
+ const beforeBlock = before === '' ? '(empty / file does not exist)' : before;
134
+ for (const line of beforeBlock.split('\n'))
135
+ lines.push(`--- ${line}`);
136
+ lines.push('');
137
+ for (const line of after.split('\n'))
138
+ lines.push(`+++ ${line}`);
139
+ lines.push('```');
140
+ return lines.join('\n');
141
+ }
142
+ /**
143
+ * Render the "after" content per change kind. For json-merge we stringify
144
+ * the patch payload; for text-insert / file-create we load the template
145
+ * from the recipe directory (when present).
146
+ */
147
+ function renderAfter(change, recipe, adopterRoot) {
148
+ if (change.kind === 'json-merge') {
149
+ return JSON.stringify(change.after ?? {}, null, 2);
150
+ }
151
+ if (change.template) {
152
+ const tplPath = locateRecipeTemplate(recipe, change.template, adopterRoot);
153
+ if (tplPath && existsSync(tplPath)) {
154
+ return safeReadFile(tplPath);
155
+ }
156
+ return `(template not found on disk: ${change.template})`;
157
+ }
158
+ if (typeof change.after === 'string')
159
+ return change.after;
160
+ return '';
161
+ }
162
+ /**
163
+ * Resolve a recipe template path relative to the framework's recipes/
164
+ * directory. The framework ships recipes at packages/framework/recipes/
165
+ * <id>/templates/. We compute the directory from this module's location
166
+ * so the resolver works regardless of cwd.
167
+ */
168
+ function locateRecipeTemplate(recipe, templateName, _adopterRoot) {
169
+ // __dirname → packages/framework/src/cli/verify-proof/sandbox/
170
+ // → up four levels → packages/framework/
171
+ // Defense-in-depth: reject `../` escapes in templateName AND validate
172
+ // recipeId so a crafted id (e.g. '../../evil') cannot shift the anchor
173
+ // outside the framework root. discoverBundledRecipes() skips schema
174
+ // validation, so the ^[a-z][a-z0-9_-]*$ pattern may not have been enforced.
175
+ if (typeof recipe.recipeId !== 'string' || !/^[a-z][a-z0-9_-]*$/.test(recipe.recipeId)) {
176
+ return null;
177
+ }
178
+ const frameworkRoot = resolve(__dirname, '..', '..', '..', '..');
179
+ const templatesRoot = join(frameworkRoot, 'recipes', recipe.recipeId, 'templates');
180
+ return safeJoinUnderRoot(templatesRoot, templateName);
181
+ }
182
+ function safeReadFile(path) {
183
+ try {
184
+ return readFileSync(path, 'utf-8');
185
+ }
186
+ catch {
187
+ return '';
188
+ }
189
+ }
190
+ //# sourceMappingURL=recipe-diff.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-diff.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/recipe-diff.mjs"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Resolve the framework's recipes directory at runtime. Works whether
3
+ * the module is loaded from source (packages/framework/src/...) or from
4
+ * the installed dist (.aperant/deps/node_modules/@aperant/framework/...).
5
+ *
6
+ * @returns {string}
7
+ */
8
+ export function resolveRecipesDir(): string;
9
+ /**
10
+ * Walk the recipes/ directory and return every recipe.json that parses.
11
+ * Each entry is the raw recipe object (NOT validated — callers can
12
+ * call validateManifest if they need schema guarantees).
13
+ *
14
+ * @returns {Array<object>}
15
+ */
16
+ export function discoverBundledRecipes(): Array<object>;
17
+ /**
18
+ * Read .aperant/config.json's verification.sandbox block, or null when
19
+ * absent / unparseable. Pure read — no defaults injected.
20
+ *
21
+ * @param {string} projectDir
22
+ * @returns {object|null}
23
+ */
24
+ export function readSandboxConfig(projectDir: string): object | null;
25
+ //# sourceMappingURL=sandbox-config.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-config.d.mts","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/sandbox-config.mjs"],"names":[],"mappings":"AA0BA;;;;;;GAMG;AACH,qCAFa,MAAM,CAMlB;AAED;;;;;;GAMG;AACH,0CAFa,KAAK,CAAC,MAAM,CAAC,CAiBzB;AAED;;;;;;GAMG;AACH,8CAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAavB"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * sandbox-config.mjs — central loader for sandbox recipes + the
3
+ * adopter's verification.sandbox config block.
4
+ *
5
+ * Two responsibilities:
6
+ *
7
+ * 1. discoverBundledRecipes() — walk packages/framework/recipes/<id>/
8
+ * and return every parseable recipe.json. Mirrors the bundled-
9
+ * drivers discovery pattern in commands/detect-runtime.mjs.
10
+ *
11
+ * 2. readSandboxConfig(projectDir) — read .aperant/config.json's
12
+ * verification.sandbox block (or null if absent). Pure read; no
13
+ * merge with config.local.json — sandbox state is shared, not
14
+ * per-device.
15
+ *
16
+ * Stays in src/cli/verify-proof/sandbox/ so consumers under that tree
17
+ * share one loader. The CLI commands (recipe-diff, apply-recipe,
18
+ * verify-sandbox) all import from here.
19
+ */
20
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
21
+ import { dirname, join, resolve } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+ /**
25
+ * Resolve the framework's recipes directory at runtime. Works whether
26
+ * the module is loaded from source (packages/framework/src/...) or from
27
+ * the installed dist (.aperant/deps/node_modules/@aperant/framework/...).
28
+ *
29
+ * @returns {string}
30
+ */
31
+ export function resolveRecipesDir() {
32
+ // __dirname → packages/framework/src/cli/verify-proof/sandbox/
33
+ // → up four levels → packages/framework/
34
+ return resolve(__dirname, '..', '..', '..', '..', 'recipes');
35
+ }
36
+ /**
37
+ * Walk the recipes/ directory and return every recipe.json that parses.
38
+ * Each entry is the raw recipe object (NOT validated — callers can
39
+ * call validateManifest if they need schema guarantees).
40
+ *
41
+ * @returns {Array<object>}
42
+ */
43
+ export function discoverBundledRecipes() {
44
+ const dir = resolveRecipesDir();
45
+ if (!existsSync(dir))
46
+ return [];
47
+ const out = [];
48
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
49
+ if (!entry.isDirectory())
50
+ continue;
51
+ const recipePath = join(dir, entry.name, 'recipe.json');
52
+ if (!existsSync(recipePath))
53
+ continue;
54
+ try {
55
+ out.push(JSON.parse(readFileSync(recipePath, 'utf-8')));
56
+ }
57
+ catch {
58
+ /* skip malformed recipes */
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+ /**
64
+ * Read .aperant/config.json's verification.sandbox block, or null when
65
+ * absent / unparseable. Pure read — no defaults injected.
66
+ *
67
+ * @param {string} projectDir
68
+ * @returns {object|null}
69
+ */
70
+ export function readSandboxConfig(projectDir) {
71
+ const path = join(resolve(projectDir), '.aperant', 'config.json');
72
+ if (!existsSync(path))
73
+ return null;
74
+ try {
75
+ const config = JSON.parse(readFileSync(path, 'utf-8'));
76
+ const v = config?.verification;
77
+ if (v === null || typeof v !== 'object')
78
+ return null;
79
+ return v.sandbox ?? null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ //# sourceMappingURL=sandbox-config.mjs.map