@aria_asi/cli 0.2.36 → 0.2.38

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 (198) hide show
  1. package/CLIENT-ONBOARDING.md +4 -2
  2. package/bin/aria.js +11 -7
  3. package/dist/aria-connector/src/auth.d.ts +14 -0
  4. package/dist/aria-connector/src/auth.d.ts.map +1 -1
  5. package/dist/aria-connector/src/auth.js +103 -1
  6. package/dist/aria-connector/src/auth.js.map +1 -1
  7. package/dist/aria-connector/src/chat.d.ts.map +1 -1
  8. package/dist/aria-connector/src/chat.js +13 -8
  9. package/dist/aria-connector/src/chat.js.map +1 -1
  10. package/dist/aria-connector/src/config.d.ts +6 -1
  11. package/dist/aria-connector/src/config.d.ts.map +1 -1
  12. package/dist/aria-connector/src/config.js.map +1 -1
  13. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  14. package/dist/aria-connector/src/connectors/claude-code.js +50 -6
  15. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  16. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  17. package/dist/aria-connector/src/connectors/codex.js +290 -32
  18. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  19. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  20. package/dist/aria-connector/src/connectors/opencode.js +35 -11
  21. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  22. package/dist/aria-connector/src/connectors/repo-guard.d.ts +10 -0
  23. package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -1
  24. package/dist/aria-connector/src/connectors/repo-guard.js +110 -164
  25. package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -1
  26. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
  27. package/dist/aria-connector/src/connectors/runtime.js +17 -7
  28. package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
  29. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  30. package/dist/aria-connector/src/connectors/shell.js +12 -8
  31. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  32. package/dist/aria-connector/src/harness-client.d.ts +3 -1
  33. package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
  34. package/dist/aria-connector/src/harness-client.js +7 -20
  35. package/dist/aria-connector/src/harness-client.js.map +1 -1
  36. package/dist/aria-connector/src/model-context.d.ts.map +1 -1
  37. package/dist/aria-connector/src/model-context.js +5 -0
  38. package/dist/aria-connector/src/model-context.js.map +1 -1
  39. package/dist/aria-connector/src/providers/types.d.ts +1 -1
  40. package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
  41. package/dist/aria-connector/src/providers/xai.d.ts +3 -0
  42. package/dist/aria-connector/src/providers/xai.d.ts.map +1 -0
  43. package/dist/aria-connector/src/providers/xai.js +40 -0
  44. package/dist/aria-connector/src/providers/xai.js.map +1 -0
  45. package/dist/aria-connector/src/setup-wizard.js +1 -0
  46. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  47. package/dist/aria-connector/src/types.d.ts +2 -0
  48. package/dist/aria-connector/src/types.d.ts.map +1 -1
  49. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +51 -9
  50. package/dist/assets/hooks/aria-first-class-coach.mjs +129 -0
  51. package/dist/assets/hooks/aria-harness-via-sdk.mjs +33 -6
  52. package/dist/assets/hooks/aria-pre-tool-gate.mjs +86 -8
  53. package/dist/assets/hooks/aria-pre-tool-use.mjs +75 -0
  54. package/dist/assets/hooks/aria-preprompt-consult.mjs +5 -6
  55. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +5 -0
  56. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +15 -0
  57. package/dist/assets/hooks/aria-stop-gate.mjs +125 -17
  58. package/dist/assets/hooks/doctrine_trigger_map.json +11 -0
  59. package/dist/assets/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  60. package/dist/assets/hooks/lib/emergency-gateoff.mjs +6 -0
  61. package/dist/assets/hooks/lib/first-class-coach.mjs +755 -0
  62. package/dist/assets/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  63. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -14
  64. package/dist/assets/opencode-plugins/harness-context/auth-token.mjs +126 -0
  65. package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +62 -22
  66. package/dist/assets/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  67. package/dist/assets/opencode-plugins/harness-gate/index.js +87 -27
  68. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  69. package/dist/assets/opencode-plugins/harness-outcome/index.js +29 -24
  70. package/dist/assets/opencode-plugins/harness-stop/index.js +229 -68
  71. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  72. package/dist/runtime/auth-token.mjs +121 -0
  73. package/dist/runtime/coach-kernel.mjs +377 -0
  74. package/dist/runtime/codex-bridge.mjs +440 -69
  75. package/dist/runtime/discipline/doctrine_trigger_map.json +11 -0
  76. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  77. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  78. package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  79. package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  80. package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  81. package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  82. package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  83. package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +18 -0
  84. package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +18 -0
  85. package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  86. package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +18 -0
  87. package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  88. package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  89. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +18 -0
  90. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +18 -0
  91. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +18 -0
  92. package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +18 -0
  93. package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +18 -0
  94. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +18 -0
  95. package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +18 -0
  96. package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +18 -0
  97. package/dist/runtime/doctrine_trigger_map.json +11 -0
  98. package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +51 -9
  99. package/dist/runtime/hooks/aria-first-class-coach.mjs +129 -0
  100. package/dist/runtime/hooks/aria-harness-via-sdk.mjs +33 -6
  101. package/dist/runtime/hooks/aria-pre-tool-gate.mjs +86 -8
  102. package/dist/runtime/hooks/aria-pre-tool-use.mjs +75 -0
  103. package/dist/runtime/hooks/aria-preprompt-consult.mjs +5 -6
  104. package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +5 -0
  105. package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +15 -0
  106. package/dist/runtime/hooks/aria-stop-gate.mjs +125 -17
  107. package/dist/runtime/hooks/doctrine_trigger_map.json +11 -0
  108. package/dist/runtime/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  109. package/dist/runtime/hooks/lib/emergency-gateoff.mjs +6 -0
  110. package/dist/runtime/hooks/lib/first-class-coach.mjs +755 -0
  111. package/dist/runtime/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  112. package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +1 -14
  113. package/dist/runtime/local-phase.mjs +8 -0
  114. package/dist/runtime/manifest.json +2 -2
  115. package/dist/runtime/provider-proxy.mjs +136 -33
  116. package/dist/runtime/sdk/BUNDLED.json +2 -2
  117. package/dist/runtime/sdk/auth.d.ts +17 -0
  118. package/dist/runtime/sdk/auth.js +158 -0
  119. package/dist/runtime/sdk/auth.js.map +1 -0
  120. package/dist/runtime/sdk/index.d.ts +8 -1
  121. package/dist/runtime/sdk/index.js +15 -1
  122. package/dist/runtime/sdk/index.js.map +1 -1
  123. package/dist/runtime/service.mjs +1711 -74
  124. package/dist/runtime/task-project-ledger.mjs +290 -0
  125. package/dist/sdk/BUNDLED.json +2 -2
  126. package/dist/sdk/auth.d.ts +17 -0
  127. package/dist/sdk/auth.js +158 -0
  128. package/dist/sdk/auth.js.map +1 -0
  129. package/dist/sdk/index.d.ts +8 -1
  130. package/dist/sdk/index.js +15 -1
  131. package/dist/sdk/index.js.map +1 -1
  132. package/hooks/aria-cognition-substrate-binding.mjs +51 -9
  133. package/hooks/aria-first-class-coach.mjs +129 -0
  134. package/hooks/aria-harness-via-sdk.mjs +33 -6
  135. package/hooks/aria-pre-tool-gate.mjs +86 -8
  136. package/hooks/aria-pre-tool-use.mjs +75 -0
  137. package/hooks/aria-preprompt-consult.mjs +5 -6
  138. package/hooks/aria-preturn-memory-gate.mjs +5 -0
  139. package/hooks/aria-repo-doctrine-gate.mjs +15 -0
  140. package/hooks/aria-stop-gate.mjs +125 -17
  141. package/hooks/doctrine_trigger_map.json +11 -0
  142. package/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  143. package/hooks/lib/emergency-gateoff.mjs +6 -0
  144. package/hooks/lib/first-class-coach.mjs +755 -0
  145. package/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  146. package/hooks/lib/skill-autoload-gate.mjs +1 -14
  147. package/opencode-plugins/harness-context/auth-token.mjs +126 -0
  148. package/opencode-plugins/harness-context/inject-context.mjs +62 -22
  149. package/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  150. package/opencode-plugins/harness-gate/index.js +87 -27
  151. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  152. package/opencode-plugins/harness-outcome/index.js +29 -24
  153. package/opencode-plugins/harness-stop/index.js +229 -68
  154. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  155. package/package.json +8 -2
  156. package/runtime-src/auth-token.mjs +121 -0
  157. package/runtime-src/coach-kernel.mjs +377 -0
  158. package/runtime-src/codex-bridge.mjs +440 -69
  159. package/runtime-src/local-phase.mjs +8 -0
  160. package/runtime-src/provider-proxy.mjs +136 -33
  161. package/runtime-src/service.mjs +1711 -74
  162. package/scripts/bundle-sdk.mjs +8 -0
  163. package/scripts/check-client-compatibility.mjs +422 -0
  164. package/scripts/check-coach-kernel.mjs +204 -0
  165. package/scripts/check-managed-runtime-ledger.mjs +107 -0
  166. package/scripts/check-opencode-config-contract.mjs +78 -0
  167. package/scripts/check-quality-ledger.mjs +121 -0
  168. package/scripts/self-test-harness-gates.mjs +179 -11
  169. package/scripts/self-test-repo-guard.mjs +38 -0
  170. package/scripts/validate-skill-prompts.mjs +14 -1
  171. package/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  172. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  173. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  174. package/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  175. package/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  176. package/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  177. package/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  178. package/skills/aria-cognition/mizan/SKILL.md +18 -0
  179. package/skills/aria-cognition/nadia/SKILL.md +18 -0
  180. package/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  181. package/skills/aria-cognition/predictor/SKILL.md +18 -0
  182. package/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  183. package/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  184. package/src/auth.ts +136 -1
  185. package/src/chat.ts +13 -8
  186. package/src/config.ts +6 -1
  187. package/src/connectors/claude-code.ts +62 -18
  188. package/src/connectors/codex.ts +288 -32
  189. package/src/connectors/opencode.ts +35 -12
  190. package/src/connectors/repo-guard.ts +117 -172
  191. package/src/connectors/runtime.ts +19 -7
  192. package/src/connectors/shell.ts +12 -8
  193. package/src/harness-client.ts +8 -22
  194. package/src/model-context.ts +6 -0
  195. package/src/providers/types.ts +1 -1
  196. package/src/providers/xai.ts +55 -0
  197. package/src/setup-wizard.ts +1 -0
  198. package/src/types.ts +2 -0
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const here = dirname(fileURLToPath(import.meta.url));
8
+ const repoRoot = resolve(here, '..', '..', '..');
9
+ const packageRoot = resolve(here, '..');
10
+ const failures = [];
11
+
12
+ function fail(check, message, details = {}) {
13
+ failures.push({ check, message, details });
14
+ }
15
+
16
+ function readText(pathname) {
17
+ return existsSync(pathname) ? readFileSync(pathname, 'utf8') : '';
18
+ }
19
+
20
+ function readJson(pathname) {
21
+ try {
22
+ return JSON.parse(readText(pathname));
23
+ } catch (error) {
24
+ fail('ledger-json', `invalid JSON at ${pathname}: ${error instanceof Error ? error.message : String(error)}`);
25
+ return null;
26
+ }
27
+ }
28
+
29
+ const ledgerPath = resolve(repoRoot, 'docs', 'system', 'ARIA_MANAGED_RUNTIME_PROVIDER_QUALITY_LEDGER.json');
30
+ const servicePath = resolve(packageRoot, 'runtime-src', 'service.mjs');
31
+ const providerProxyPath = resolve(packageRoot, 'runtime-src', 'provider-proxy.mjs');
32
+ const packageJsonPath = resolve(packageRoot, 'package.json');
33
+
34
+ const ledger = readJson(ledgerPath);
35
+ const service = readText(servicePath);
36
+ const providerProxy = readText(providerProxyPath);
37
+ const packageJson = readJson(packageJsonPath);
38
+
39
+ if (!ledger) {
40
+ fail('managed-ledger', 'managed runtime provider ledger is missing or invalid');
41
+ } else {
42
+ if (ledger.schemaVersion !== 1) fail('managed-ledger.schemaVersion', 'schemaVersion must be 1');
43
+ if (ledger.ledgerId !== 'aria-managed-runtime-provider-quality-ledger') fail('managed-ledger.ledgerId', 'unexpected managed ledger id');
44
+ if (ledger.readinessClaimAllowed === true && Array.isArray(ledger.openBlockers) && ledger.openBlockers.length > 0) {
45
+ fail('managed-ledger.readinessClaimAllowed', 'readiness cannot be true while open blockers remain');
46
+ }
47
+ const gates = new Set((ledger.qualityGates || []).map((gate) => gate?.id).filter(Boolean));
48
+ for (const gate of ['pre_generation_binding_gate', 'skill_autoload_gate', 'repair_first_gate', 'client_safe_ux_gate', 'credential_plane_gate', 'runtime_ledger_enforcement_gate']) {
49
+ if (!gates.has(gate)) fail('managed-ledger.qualityGates', `missing quality gate ${gate}`);
50
+ }
51
+ }
52
+
53
+ if (!service) {
54
+ fail('runtime-service', 'runtime service source is missing');
55
+ } else {
56
+ for (const token of [
57
+ 'MANAGED_RUNTIME_LEDGER_PATH',
58
+ 'appendManagedRuntimeLedger',
59
+ 'buildManagedRuntimeLedgerRecord',
60
+ 'classifyRuntimeRequiredSkills',
61
+ 'loadRuntimeRequiredSkills',
62
+ 'buildForcedSkillPromptBlock',
63
+ 'ARIA RUNTIME FORCED SKILL LOAD',
64
+ 'pre_provider_call',
65
+ 'provider_candidate_evaluated',
66
+ 'repair_before_provider_call',
67
+ 'repair_candidate_evaluated',
68
+ 'final_release_decision',
69
+ 'harness_packet_hash',
70
+ 'loaded_skill_ids',
71
+ 'loaded_skill_hashes',
72
+ 'credential_plane',
73
+ ]) {
74
+ if (!service.includes(token)) fail('runtime-service.ledger-contract', `missing runtime ledger token ${token}`);
75
+ }
76
+
77
+ const preCallIndex = service.indexOf("phase: 'pre_provider_call'");
78
+ const providerCallIndex = service.indexOf('providerMeta = await callProviderByStyle');
79
+ if (preCallIndex < 0 || providerCallIndex < 0 || preCallIndex > providerCallIndex) {
80
+ fail('runtime-service.pre-provider-order', 'managed ledger pre_provider_call record must be written before upstream provider call');
81
+ }
82
+
83
+ const repairRecordIndex = service.indexOf("phase: 'repair_before_provider_call'");
84
+ const repairCallIndex = service.indexOf('const recoveryMeta = await callProviderByStyle');
85
+ if (repairRecordIndex < 0 || repairCallIndex < 0 || repairRecordIndex > repairCallIndex) {
86
+ fail('runtime-service.repair-order', 'repair ledger record must be written before regenerated provider call');
87
+ }
88
+ }
89
+
90
+ if (!providerProxy.includes('function secretEnv(')) {
91
+ fail('provider-proxy.secret-env', 'provider proxy must define secretEnv before reading secret-backed provider env vars');
92
+ }
93
+
94
+ const scripts = packageJson?.scripts || {};
95
+ if (!scripts['check:managed-runtime-ledger']) {
96
+ fail('package.scripts.check:managed-runtime-ledger', 'package must expose managed runtime ledger check');
97
+ }
98
+
99
+ const result = {
100
+ ok: failures.length === 0,
101
+ checkedAt: new Date().toISOString(),
102
+ ledgerPath,
103
+ servicePath,
104
+ failures,
105
+ };
106
+ console.log(JSON.stringify(result, null, 2));
107
+ process.exit(result.ok ? 0 : 1);
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+ const packageRoot = resolve(here, '..');
10
+ const home = homedir();
11
+ const failures = [];
12
+
13
+ function fail(check, message, details = {}) {
14
+ failures.push({ check, message, details });
15
+ }
16
+
17
+ function readText(pathname) {
18
+ return existsSync(pathname) ? readFileSync(pathname, 'utf8') : '';
19
+ }
20
+
21
+ function readJsonIfExists(pathname) {
22
+ if (!existsSync(pathname)) return null;
23
+ try {
24
+ return JSON.parse(readFileSync(pathname, 'utf8'));
25
+ } catch (error) {
26
+ fail('opencode-config-json', `invalid JSON at ${pathname}: ${error instanceof Error ? error.message : String(error)}`);
27
+ return null;
28
+ }
29
+ }
30
+
31
+ const connectorSourcePath = resolve(packageRoot, 'src', 'connectors', 'opencode.ts');
32
+ const connectorSource = readText(connectorSourcePath);
33
+ if (!connectorSource) {
34
+ fail('opencode-connector-source', 'OpenCode connector source is missing');
35
+ } else {
36
+ if (/agentsMdPath\s*=|agentsMdPath['"]?\s*:/.test(connectorSource)) {
37
+ fail('opencode-connector-source.agentsMdPath', 'connector must not write unsupported OpenCode agentsMdPath config key');
38
+ }
39
+ for (const token of ['packageOpenCodeHooksDir', "path.join(opencodeDir, 'hooks')", "path.join(homedir(), '.config', 'opencode', 'config.json')"]) {
40
+ if (!connectorSource.includes(token)) fail('opencode-connector-source.contract', `missing connector contract token ${token}`);
41
+ }
42
+ }
43
+
44
+ const activeConfigPaths = [
45
+ join(home, '.opencode', 'config.json'),
46
+ join(home, '.config', 'opencode', 'config.json'),
47
+ ];
48
+ for (const configPath of activeConfigPaths) {
49
+ const config = readJsonIfExists(configPath);
50
+ if (!config) continue;
51
+ if (Object.prototype.hasOwnProperty.call(config, 'agentsMdPath')) {
52
+ fail('opencode-active-config.agentsMdPath', 'active OpenCode config contains unsupported agentsMdPath key', { configPath });
53
+ }
54
+ const provider = config.provider && typeof config.provider === 'object' ? config.provider : {};
55
+ const aria = provider.aria && typeof provider.aria === 'object' ? provider.aria : null;
56
+ if (aria) {
57
+ if (aria.endpoint !== 'http://127.0.0.1:4319/v1') {
58
+ fail('opencode-active-config.aria-endpoint', 'active aria provider endpoint must point to mounted runtime v1 endpoint', { configPath, endpoint: aria.endpoint });
59
+ }
60
+ if (aria.name !== 'Aria Runtime') {
61
+ fail('opencode-active-config.aria-name', 'active aria provider must be named Aria Runtime', { configPath, name: aria.name });
62
+ }
63
+ }
64
+ }
65
+
66
+ const installedHelper = join(home, '.opencode', 'hooks', 'lib', 'skill-autoload-gate.mjs');
67
+ const sourceHelper = resolve(packageRoot, 'hooks', 'lib', 'skill-autoload-gate.mjs');
68
+ if (!existsSync(sourceHelper)) fail('opencode-source-hook-helper', 'bundled OpenCode hook helper is missing');
69
+ if (!existsSync(installedHelper)) fail('opencode-installed-hook-helper', 'installed OpenCode hook helper is missing');
70
+
71
+ const result = {
72
+ ok: failures.length === 0,
73
+ checkedAt: new Date().toISOString(),
74
+ checkedConfigs: activeConfigPaths.filter((configPath) => existsSync(configPath)),
75
+ failures,
76
+ };
77
+ console.log(JSON.stringify(result, null, 2));
78
+ process.exit(result.ok ? 0 : 1);
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const here = dirname(fileURLToPath(import.meta.url));
8
+ const repoRoot = resolve(here, '..', '..', '..');
9
+ const packageRoot = resolve(here, '..');
10
+ const ledgerPath = resolve(repoRoot, 'docs', 'system', 'ARIA_CLIENT_COMPATIBILITY_QUALITY_LEDGER.json');
11
+ const failures = [];
12
+
13
+ const PROOF_STATUSES = new Set(['pending', 'in_progress', 'completed', 'blocked']);
14
+
15
+ function fail(path, message, details = {}) {
16
+ failures.push({ path, message, details });
17
+ }
18
+
19
+ function asArray(value) {
20
+ return Array.isArray(value) ? value : [];
21
+ }
22
+
23
+ function readJson(pathname) {
24
+ if (!existsSync(pathname)) return null;
25
+ try {
26
+ return JSON.parse(readFileSync(pathname, 'utf8'));
27
+ } catch (error) {
28
+ fail(pathname, `invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
29
+ return null;
30
+ }
31
+ }
32
+
33
+ const ledger = readJson(ledgerPath);
34
+ const packageJson = readJson(resolve(packageRoot, 'package.json'));
35
+
36
+ if (!ledger) {
37
+ fail('ledger', 'Client compatibility quality ledger is missing.');
38
+ } else {
39
+ if (ledger.schemaVersion !== 1) fail('schemaVersion', 'Expected schemaVersion 1.');
40
+ if (ledger.ledgerId !== 'aria-client-compatibility-quality-ledger') fail('ledgerId', 'Unexpected ledger id.');
41
+ if (!ledger.ownerIntent || typeof ledger.ownerIntent !== 'string') fail('ownerIntent', 'ownerIntent is required.');
42
+ if (!ledger.releaseRule || typeof ledger.releaseRule !== 'string') fail('releaseRule', 'releaseRule is required.');
43
+ if (!Array.isArray(ledger.nonDriftRules) || ledger.nonDriftRules.length < 5) fail('nonDriftRules', 'At least five non-drift rules are required.');
44
+
45
+ const gateDefinitions = asArray(ledger.qualityGateDefinitions);
46
+ const gateIds = new Set(gateDefinitions.map((gate) => gate?.id).filter(Boolean));
47
+ if (gateDefinitions.length < 8) fail('qualityGateDefinitions', 'Expected at least eight quality gates.');
48
+ for (const gate of gateDefinitions) {
49
+ if (!gate?.id) fail('qualityGateDefinitions', 'Every gate needs an id.');
50
+ if (!gate?.description) fail(`qualityGateDefinitions.${gate?.id || 'unknown'}`, 'Every gate needs a description.');
51
+ }
52
+
53
+ const requiredGateIds = [
54
+ 'source_wrapper_contract',
55
+ 'active_wrapper_contract',
56
+ 'claude_black_box_contract',
57
+ 'codex_black_box_contract',
58
+ 'runtime_stale_path_contract',
59
+ 'provider_model_contract',
60
+ 'closeout_verifier_contract',
61
+ 'ledger_contract',
62
+ ];
63
+ for (const gateId of requiredGateIds) {
64
+ if (!gateIds.has(gateId)) fail('qualityGateDefinitions', `Missing required gate: ${gateId}`);
65
+ }
66
+
67
+ const platforms = asArray(ledger.platformLifecycleMatrix);
68
+ if (platforms.length < 4) fail('platformLifecycleMatrix', 'Expected Claude, Codex, runtime, and release governance rows.');
69
+ for (const platform of platforms) {
70
+ if (!platform?.platform) fail('platformLifecycleMatrix', 'Every platform row needs a platform id.');
71
+ if (!Array.isArray(platform?.requiredChecks) || platform.requiredChecks.length === 0) {
72
+ fail(`platformLifecycleMatrix.${platform?.platform || 'unknown'}.requiredChecks`, 'Every platform row needs required checks.');
73
+ }
74
+ for (const check of asArray(platform?.requiredChecks)) {
75
+ if (!gateIds.has(check)) fail(`platformLifecycleMatrix.${platform?.platform || 'unknown'}.requiredChecks`, `Unknown required check: ${check}`);
76
+ }
77
+ if (!PROOF_STATUSES.has(platform?.runtimeProofStatus)) {
78
+ fail(`platformLifecycleMatrix.${platform?.platform || 'unknown'}.runtimeProofStatus`, `Invalid runtime proof status: ${String(platform?.runtimeProofStatus)}`);
79
+ }
80
+ if (platform?.promotionAllowed === true && platform.runtimeProofStatus !== 'completed') {
81
+ fail(`platformLifecycleMatrix.${platform.platform}.promotionAllowed`, 'Promotion cannot be allowed unless runtime proof is completed.');
82
+ }
83
+ }
84
+
85
+ const requiredCommands = asArray(ledger.requiredCommands);
86
+ const commandIds = new Set(requiredCommands.map((command) => command?.id).filter(Boolean));
87
+ for (const commandId of ['connector_typecheck', 'hook_contracts', 'client_compatibility', 'closeout_verifier']) {
88
+ if (!commandIds.has(commandId)) fail('requiredCommands', `Missing required command: ${commandId}`);
89
+ }
90
+ if (ledger.readinessClaimAllowed === true) {
91
+ const incompletePlatforms = platforms.filter((platform) => platform.runtimeProofStatus !== 'completed' || platform.promotionAllowed !== true);
92
+ const blockers = asArray(ledger.openBlockers);
93
+ if (incompletePlatforms.length > 0) fail('readinessClaimAllowed', 'Readiness cannot be allowed with incomplete platform runtime proof.', { incompletePlatforms: incompletePlatforms.map((platform) => platform.platform) });
94
+ if (blockers.length > 0) fail('readinessClaimAllowed', 'Readiness cannot be allowed while open blockers remain.', { blockers: blockers.map((blocker) => blocker.id) });
95
+ }
96
+ }
97
+
98
+ if (!packageJson) {
99
+ fail('package.json', 'package.json is missing or invalid.');
100
+ } else {
101
+ const scripts = packageJson.scripts || {};
102
+ if (!scripts['check:client-compat']) fail('package.scripts.check:client-compat', 'Package must expose the client compatibility gate.');
103
+ if (!scripts['check:quality-ledger']) fail('package.scripts.check:quality-ledger', 'Package must expose the quality ledger gate.');
104
+ if (!scripts['check:release']) fail('package.scripts.check:release', 'Package must expose the release gate.');
105
+ }
106
+
107
+ if (!existsSync(resolve(repoRoot, 'harness', 'aria-verify.mjs'))) {
108
+ fail('harness.aria-verify', 'Closeout verifier script is missing.');
109
+ }
110
+ if (!existsSync(resolve(packageRoot, 'scripts', 'check-client-compatibility.mjs'))) {
111
+ fail('scripts.check-client-compatibility', 'Client compatibility gate script is missing.');
112
+ }
113
+
114
+ const result = {
115
+ ok: failures.length === 0,
116
+ ledgerPath,
117
+ checkedAt: new Date().toISOString(),
118
+ failures,
119
+ };
120
+ console.log(JSON.stringify(result, null, 2));
121
+ process.exit(result.ok ? 0 : 1);
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import assert from 'node:assert/strict';
4
- import { mkdtempSync, rmSync } from 'node:fs';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
5
6
  import { tmpdir } from 'node:os';
6
7
  import { join } from 'node:path';
7
8
  import { pathToFileURL } from 'node:url';
@@ -9,6 +10,23 @@ import { pathToFileURL } from 'node:url';
9
10
  const repoRoot = join(import.meta.dirname, '..', '..', '..');
10
11
  const gateModule = await import(pathToFileURL(join(repoRoot, 'ops/claude-hooks/lib/skill-autoload-gate.mjs')));
11
12
 
13
+ async function captureStderr(fn) {
14
+ const originalWrite = process.stderr.write;
15
+ let captured = '';
16
+ process.stderr.write = (chunk, encoding, callback) => {
17
+ captured += String(chunk || '');
18
+ if (typeof encoding === 'function') encoding();
19
+ if (typeof callback === 'function') callback();
20
+ return true;
21
+ };
22
+ try {
23
+ const value = await fn();
24
+ return { value, captured };
25
+ } finally {
26
+ process.stderr.write = originalWrite;
27
+ }
28
+ }
29
+
12
30
  assert.equal(typeof gateModule.evaluateSkillGate, 'function', 'evaluateSkillGate export missing');
13
31
  assert.equal(typeof gateModule.formatSkillGateBlock, 'function', 'formatSkillGateBlock export missing');
14
32
 
@@ -19,10 +37,8 @@ const broadClaim = gateModule.evaluateSkillGate({
19
37
  text: 'This is production-ready in general for client npm packages, SDKs, runtimes, and harnesses.',
20
38
  autoLoadAvailable: false,
21
39
  });
22
- assert.equal(broadClaim.ok, false, 'broad readiness claim must block without required skills');
23
- assert.ok(broadClaim.missingSkills.includes('architecture-decision'), 'architecture-decision must be required');
24
- assert.ok(broadClaim.missingSkills.includes('testing-strategy'), 'testing-strategy must be required');
25
- assert.ok(broadClaim.missingSkills.includes('aria-forge-guardrails'), 'aria-forge-guardrails must be required');
40
+ assert.equal(broadClaim.redirectOnly, true, 'broad readiness language must redirect/teach instead of creating a permanent block');
41
+ assert.ok(broadClaim.reasons.some((reason) => /recovery guidance/.test(reason)), 'broad readiness must carry recovery guidance');
26
42
 
27
43
  const deployAction = gateModule.evaluateSkillGate({
28
44
  sessionId: 'self-test-deploy',
@@ -36,6 +52,42 @@ const deployAction = gateModule.evaluateSkillGate({
36
52
  assert.equal(deployAction.ok, false, 'deploy action must block without aria-harness-deploy');
37
53
  assert.ok(deployAction.missingSkills.includes('aria-harness-deploy'), 'aria-harness-deploy must be required');
38
54
 
55
+ const deployActionWithContractEvidence = gateModule.evaluateSkillGate({
56
+ sessionId: 'self-test-deploy-contract-evidence',
57
+ surface: 'self-test-action',
58
+ isDeploy: true,
59
+ toolName: 'Bash',
60
+ action: [
61
+ '<verify>',
62
+ 'target: deployment/test namespace test replicas 1',
63
+ 'role: test service',
64
+ 'verified: syntax check exit=0',
65
+ 'rollback: rollout undo test',
66
+ 'axiom: truth_over_deception',
67
+ '</verify>',
68
+ '<cognition>',
69
+ 'nur: observed state with doctrine:feedback_no_assumption_without_verification and axiom:truth_over_deception',
70
+ 'mizan: proportionate gate with doctrine:feedback_deploy_requires_verify_cognition and axiom:reflection_before_action',
71
+ 'hikma: policy proof with doctrine:feedback_admission_policy_verification and axiom:sacred_trust',
72
+ 'tafakkur: structural read with doctrine:feedback_dalio_expected_required and axiom:truth_over_deception',
73
+ 'tadabbur: if check passes continue with doctrine:feedback_no_assumption_without_verification and axiom:reflection_before_action',
74
+ 'ilham: preserve mechanism with doctrine:feedback_admission_policy_verification and axiom:sacred_trust',
75
+ 'wahi: proof landed with doctrine:feedback_deploy_requires_verify_cognition and axiom:truth_over_deception',
76
+ 'firasah: owner needs true state with doctrine:feedback_deploy_requires_verify_cognition and axiom:sacred_trust',
77
+ '</cognition>',
78
+ '<expected>',
79
+ 'predicate: exit=0',
80
+ 'measurable_type: boolean',
81
+ '</expected>',
82
+ 'bash scripts/deploy-service.sh test',
83
+ ].join('\n'),
84
+ text: 'deploy service with full contract evidence and transcript-invisible skill evidence',
85
+ autoLoadAvailable: false,
86
+ });
87
+ assert.equal(deployActionWithContractEvidence.ok, true, gateModule.formatSkillGateBlock(deployActionWithContractEvidence));
88
+ assert.ok(deployActionWithContractEvidence.contractEvidence.includes('aria-harness-deploy'), 'deploy contract evidence must satisfy deploy skill');
89
+ assert.ok(deployActionWithContractEvidence.contractEvidence.includes('aria-forge-guardrails'), 'deploy contract evidence must satisfy forge guardrails');
90
+
39
91
  const missingProof = gateModule.evaluateSkillGate({
40
92
  sessionId: 'self-test-missing-proof',
41
93
  surface: 'self-test-output',
@@ -46,12 +98,32 @@ const missingProof = gateModule.evaluateSkillGate({
46
98
  ].join('\n'),
47
99
  autoLoadAvailable: false,
48
100
  });
49
- assert.equal(missingProof.ok, false, 'completion with failed/missing proof must block');
50
- assert.ok(missingProof.recoveryMissing.includes('successful proof from a concrete command/probe'), 'successful proof must be required');
51
- assert.ok(missingProof.recoveryMissing.includes('re-submission'), 're-submission must be required');
52
- assert.ok(missingProof.recoveryMissing.includes('re-write'), 're-write must be required');
53
- assert.ok(missingProof.recoveryMissing.includes('re-test'), 're-test must be required');
54
- assert.ok(missingProof.recoveryMissing.includes('ARIA console escalation'), 'ARIA console escalation must be required');
101
+ assert.equal(missingProof.ok, true, 'completion with explicit unresolved state must redirect instead of permanently blocking');
102
+ assert.ok(missingProof.reasons.some((reason) => /unresolved state/.test(reason)), 'unresolved state must be recognized');
103
+
104
+ const skillRecoveryBlock = gateModule.formatSkillGateBlock(missingProof);
105
+ assert.match(skillRecoveryBlock, /Recovery contract:/, 'skill block must include recovery contract');
106
+ assert.match(skillRecoveryBlock, /<applied_cognition>/, 'skill block must include applied cognition template');
107
+ assert.doesNotMatch(skillRecoveryBlock, /counter_action:/, 'skill block must not emit old counter_action-only recovery');
108
+ assert.doesNotMatch(skillRecoveryBlock, /Required repair:/, 'skill block must not emit out-of-contract repair text');
109
+
110
+ const postCompactSkillGate = gateModule.evaluateSkillGate({
111
+ sessionId: 'self-test-post-compact-skill',
112
+ surface: 'opencode-harness-stop',
113
+ isOutputCloseout: true,
114
+ text: [
115
+ 'post_compact_continuation',
116
+ 'post commission',
117
+ 'current objective: continue the recovery without bricking.',
118
+ 'known blockers: prior skill-autoload block included deploy/readiness words.',
119
+ 'next executable step: run the gate self-test.',
120
+ 'what not to touch: unrelated dirty files.',
121
+ 'verification already run: not run.',
122
+ 'deploy production-ready complete blocked no proof fail open advisory remove strip test',
123
+ ].join('\n'),
124
+ autoLoadAvailable: false,
125
+ });
126
+ assert.equal(postCompactSkillGate.ok, true, gateModule.formatSkillGateBlock(postCompactSkillGate));
55
127
 
56
128
  const loaded = gateModule.evaluateSkillGate({
57
129
  sessionId: 'self-test-loaded',
@@ -70,9 +142,105 @@ assert.equal(loaded.ok, true, gateModule.formatSkillGateBlock(loaded));
70
142
 
71
143
  const installedStop = await import(pathToFileURL(join(repoRoot, 'packages/aria-connector/opencode-plugins/harness-stop/index.js')));
72
144
  assert.equal(typeof installedStop.default, 'function', 'harness-stop default export missing');
145
+ assert.equal(typeof installedStop.classifyContinuationHandoff, 'function', 'harness-stop continuation classifier export missing');
146
+
147
+ const previousGateOff = process.env.ARIA_GATES_OFF;
148
+ process.env.ARIA_GATES_OFF = '1';
149
+ try {
150
+ const { captured } = await captureStderr(async () => {
151
+ const plugin = await installedStop.default({});
152
+ await plugin['experimental.text.complete']({}, { text: [
153
+ 'This intentionally long output would normally trip the text gate because it makes a decision-bearing claim without the required recovery shape.',
154
+ 'The emergency flag must allow the owner to keep a session unbricked while preserving the underlying gate code for later manual re-enable.',
155
+ 'The content is long enough to exceed the non-trivial threshold and would otherwise be inspected by local output rules.',
156
+ ].join(' ') });
157
+ });
158
+ assert.doesNotMatch(captured, /emergency gate-off allow/, 'emergency gate-off must not spam the OpenCode screen');
159
+ assert.match(captured, /harness-stop.*Active/, 'emergency gate-off path must still load the stop plugin');
160
+ } finally {
161
+ if (previousGateOff === undefined) delete process.env.ARIA_GATES_OFF;
162
+ else process.env.ARIA_GATES_OFF = previousGateOff;
163
+ }
164
+
165
+ const validHandoff = installedStop.classifyContinuationHandoff([
166
+ 'post_compact_continuation',
167
+ 'current objective: continue repairing the gate without claiming completion.',
168
+ 'known blockers: lane gateway backend is offline and unrelated work remains unresolved.',
169
+ 'next executable step: inspect the stop gate classifier and run the self-test.',
170
+ 'what not to touch: do not revert unrelated user changes or deploy Soul.',
171
+ 'verification already run: npm run test:executor-contract passed previously; current gate test pending.',
172
+ ].join('\n'));
173
+ assert.equal(validHandoff.isContinuation, true, 'handoff marker must classify as continuation');
174
+ assert.equal(validHandoff.ok, true, `valid handoff must pass: ${JSON.stringify(validHandoff)}`);
175
+
176
+ const incompleteHandoff = installedStop.classifyContinuationHandoff([
177
+ 'handoff_resume',
178
+ 'current objective: continue work.',
179
+ 'known blockers: unresolved.',
180
+ ].join('\n'));
181
+ assert.equal(incompleteHandoff.ok, false, 'handoff missing required fields must block');
182
+ assert.ok(incompleteHandoff.missingFields.includes('next executable step'), 'missing next executable step must be reported');
183
+ try {
184
+ const { captured } = await captureStderr(async () => {
185
+ const plugin = await installedStop.default({});
186
+ await plugin['experimental.text.complete']({}, { text: [
187
+ 'handoff_resume',
188
+ 'current objective: continue work.',
189
+ 'known blockers: unresolved.',
190
+ 'This intentionally long malformed handoff forces the stop gate to return recovery instructions instead of bricking the next session.'.repeat(4),
191
+ ].join('\n') });
192
+ });
193
+ assert.match(captured, /recovery-required/, 'malformed handoff must emit captured recovery-required diagnostics');
194
+ } catch (error) {
195
+ const message = String(error?.message || error);
196
+ assert.doesNotMatch(message, /ARIA LOCAL OUTPUT BLOCK/, 'malformed handoff must not brick the whole screen');
197
+ }
198
+ const recoveryPath = join(process.env.HOME || tmpdir(), '.aria', 'governance-recovery-current.json');
199
+ assert.equal(existsSync(recoveryPath), true, 'malformed handoff must persist recovery context');
200
+ const recoveryState = JSON.parse(readFileSync(recoveryPath, 'utf8'));
201
+ assert.equal(recoveryState.governanceMode, 'recovery-required', 'malformed handoff must require recovery');
202
+ assert.match(recoveryState.recoveryContract.blockedReason, /Recovery contract:/, 'recovery state must include recovery contract');
203
+ assert.match(recoveryState.recoveryContract.blockedReason, /post_compact_continuation/, 'recovery state must include exact continuation marker');
204
+ assert.match(recoveryState.recoveryContract.blockedReason, /next executable step:/, 'recovery state must include next executable step field');
205
+ assert.match(recoveryState.recoveryContract.retry, /One recovery attempt/, 'recovery state must include one-shot retry rule');
206
+
207
+ const fakeCompleteHandoff = installedStop.classifyContinuationHandoff([
208
+ 'post_compact_continuation',
209
+ 'current objective: everything is done and fixed.',
210
+ 'known blockers: none.',
211
+ 'next executable step: continue.',
212
+ 'what not to touch: unrelated files.',
213
+ 'verification already run: not run.',
214
+ ].join('\n'));
215
+ assert.equal(fakeCompleteHandoff.ok, false, 'handoff with fake completion claim must block');
216
+ assert.equal(fakeCompleteHandoff.hasFakeCompletionClaim, true, 'fake completion claim must be identified');
73
217
  const installedGate = await import(pathToFileURL(join(repoRoot, 'packages/aria-connector/opencode-plugins/harness-gate/index.js')));
74
218
  assert.equal(typeof installedGate.default, 'function', 'harness-gate default export missing');
75
219
 
220
+ const postCompactPayload = JSON.stringify({
221
+ text: [
222
+ 'post_compact_continuation',
223
+ 'current objective: continue gate recovery.',
224
+ 'known blockers: none.',
225
+ 'next executable step: resume the next concrete verification.',
226
+ 'what not to touch: unrelated dirty files.',
227
+ 'verification already run: current self-test in progress.',
228
+ ].join('\n'),
229
+ });
230
+ for (const gatePath of [
231
+ join(repoRoot, 'scripts/aria-governance-gate.mjs'),
232
+ join(repoRoot, 'harness/packages/harness-http-client/scripts/aria-governance-gate.mjs'),
233
+ ]) {
234
+ const child = spawnSync(process.execPath, [gatePath], {
235
+ input: `${postCompactPayload}\n`,
236
+ encoding: 'utf8',
237
+ });
238
+ assert.equal(child.status, 0, `post-compact governance allow failed for ${gatePath}: ${child.stdout}${child.stderr}`);
239
+ const result = JSON.parse(child.stdout);
240
+ assert.equal(result.decision, 'allow', `post-compact governance must allow for ${gatePath}`);
241
+ assert.equal(result.postCompactContinuation?.active, true, `post-compact marker missing for ${gatePath}`);
242
+ }
243
+
76
244
  const temp = mkdtempSync(join(tmpdir(), 'aria-harness-gates-'));
77
245
  rmSync(temp, { recursive: true, force: true });
78
246
 
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ import assert from 'node:assert/strict';
4
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { pathToFileURL } from 'node:url';
8
+ import { execFileSync } from 'node:child_process';
9
+
10
+ const repoRoot = join(import.meta.dirname, '..', '..', '..');
11
+ const modulePath = pathToFileURL(join(repoRoot, 'packages/aria-connector/dist/aria-connector/src/connectors/repo-guard.js'));
12
+ const { approveRepoGuardDelete, isRepoGuardedPath } = await import(modulePath);
13
+
14
+ assert.equal(isRepoGuardedPath('apps/service/source.ts'), true, 'repo source paths must be guarded');
15
+ assert.equal(isRepoGuardedPath('docs/important-note.md'), true, 'whole-repo docs must be guarded');
16
+ assert.equal(isRepoGuardedPath('node_modules/pkg/index.js'), false, 'node_modules must be ignored');
17
+ assert.equal(isRepoGuardedPath('apps/service/dist/index.js'), false, 'build output must be ignored');
18
+ assert.equal(isRepoGuardedPath('package-lock.json'), false, 'lockfiles are not repo-guard delete targets');
19
+
20
+ const tempRepo = mkdtempSync(join(tmpdir(), 'aria-repo-guard-'));
21
+ try {
22
+ execFileSync('git', ['init'], { cwd: tempRepo, stdio: 'ignore' });
23
+ execFileSync('git', ['config', 'user.email', 'repo-guard@example.local'], { cwd: tempRepo, stdio: 'ignore' });
24
+ execFileSync('git', ['config', 'user.name', 'Repo Guard Test'], { cwd: tempRepo, stdio: 'ignore' });
25
+ writeFileSync(join(tempRepo, 'keep.md'), 'tracked\n');
26
+ execFileSync('git', ['add', 'keep.md'], { cwd: tempRepo, stdio: 'ignore' });
27
+ execFileSync('git', ['commit', '-m', 'seed'], { cwd: tempRepo, stdio: 'ignore' });
28
+
29
+ const approval = approveRepoGuardDelete(tempRepo, 'keep.md', 'self-test', 'explicit test approval', 1000);
30
+ assert.equal(approval.repoRoot, tempRepo, 'approval must bind to the repo root');
31
+ assert.equal(approval.relPath, 'keep.md', 'approval must bind to the relative path');
32
+ assert.equal(approval.approvedBy, 'self-test', 'approval must record approver');
33
+ assert.ok(Date.parse(approval.expiresAt) > Date.now(), 'approval must be time-boxed');
34
+ } finally {
35
+ rmSync(tempRepo, { recursive: true, force: true });
36
+ }
37
+
38
+ console.log('repo guard self-test passed');
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { readdirSync, readFileSync, statSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
4
5
  import path from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
 
@@ -9,7 +10,11 @@ const repoRoot = path.resolve(here, '..', '..', '..');
9
10
  const skillRoots = [
10
11
  path.join(repoRoot, 'packages/aria-connector/skills'),
11
12
  path.join(repoRoot, 'harness/packages/harness-http-client/skills'),
12
- ];
13
+ path.join(homedir(), '.agents/skills'),
14
+ path.join(homedir(), '.claude/skills'),
15
+ ].filter((root) => {
16
+ try { return statSync(root).isDirectory(); } catch { return false; }
17
+ });
13
18
 
14
19
  function walkSkills(dir, acc = []) {
15
20
  for (const name of readdirSync(dir)) {
@@ -77,6 +82,14 @@ for (const root of skillRoots) {
77
82
  warnings.push(`${rel}: no explicit workflow section`);
78
83
  }
79
84
 
85
+ if (!hasSection(text, ['First-Class Production Contract'])) {
86
+ errors.push(`${rel}: missing First-Class Production Contract section`);
87
+ }
88
+
89
+ if (!/\b(no placeholders|no fake fallbacks|real proof|architect\/ARIA console|re-test|redo contract)\b/i.test(text)) {
90
+ errors.push(`${rel}: first-class contract must require no placeholders, no fake fallbacks, real proof, re-test, redo contract, and architect/ARIA console escalation`);
91
+ }
92
+
80
93
  if (/\bcoming soon\b/i.test(text)) {
81
94
  warnings.push(`${rel}: contains deferred-language phrase`);
82
95
  }
@@ -61,3 +61,21 @@ For non-trivial work, make sure your internal workflow yields at least:
61
61
  - [references/domain-matrix.md](references/domain-matrix.md)
62
62
  - [references/evolution-loop.md](references/evolution-loop.md)
63
63
  - [references/readable-cognition.md](references/readable-cognition.md)
64
+
65
+ ## First-Class Production Contract
66
+
67
+ This skill must deepen the work, not decorate it. When loaded, apply it as a production requirement:
68
+
69
+ - no placeholders, no fake fallbacks, no vague future wiring, and no cosmetic compliance
70
+ - real proof from the repo, runtime, tests, logs, endpoint, or owner instruction before any completion claim
71
+ - a redo contract whenever the skill blocks or finds a gap: what failed, what to change, how to re-test, and when to re-submit
72
+ - architect/ARIA console escalation when the same failure repeats twice, crosses system boundaries, or cannot be verified locally
73
+ - owner-visible output that states the exact state of the work: changed, verified, blocked, or needing a decision
74
+ ## Self-Executing Recovery Contract
75
+
76
+ When this skill blocks, fails verification, or finds a gap, the agent must execute recovery rather than hand work back as a user-only decision.
77
+
78
+ - Make exactly one self-executed recovery attempt using the strongest safe local, repository, runtime, test, log, endpoint, or owner-provided evidence available.
79
+ - If the same failure repeats after that one recovery attempt, switch to architect execution mode: operate under the architect/ARIA harness to diagnose, choose the next action, execute the repair path, and verify the result.
80
+ - If Aria or the architect service is unavailable, fall back to the strongest available client LLM under the architect harness or strongest available local harness substrate; mark the fallback explicitly and continue recovery instead of stopping.
81
+ - Do not end with user-handoff-only behavior. If blocked after the recovery path, report the executed recovery attempt, evidence, remaining blocker, and the next concrete action or owner decision needed; do not merely ask the user to take over.