@aria_asi/cli 0.2.36 → 0.2.37

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 (195) 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 +310 -10
  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 +33 -8
  53. package/dist/assets/hooks/aria-preprompt-consult.mjs +5 -6
  54. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +5 -0
  55. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +15 -0
  56. package/dist/assets/hooks/aria-stop-gate.mjs +125 -17
  57. package/dist/assets/hooks/doctrine_trigger_map.json +11 -0
  58. package/dist/assets/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  59. package/dist/assets/hooks/lib/emergency-gateoff.mjs +6 -0
  60. package/dist/assets/hooks/lib/first-class-coach.mjs +755 -0
  61. package/dist/assets/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  62. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -14
  63. package/dist/assets/opencode-plugins/harness-context/auth-token.mjs +126 -0
  64. package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +62 -22
  65. package/dist/assets/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  66. package/dist/assets/opencode-plugins/harness-gate/index.js +87 -27
  67. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  68. package/dist/assets/opencode-plugins/harness-outcome/index.js +29 -24
  69. package/dist/assets/opencode-plugins/harness-stop/index.js +229 -68
  70. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  71. package/dist/runtime/auth-token.mjs +121 -0
  72. package/dist/runtime/coach-kernel.mjs +371 -0
  73. package/dist/runtime/codex-bridge.mjs +440 -69
  74. package/dist/runtime/discipline/doctrine_trigger_map.json +11 -0
  75. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  76. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  77. package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  78. package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  79. package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  80. package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  81. package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  82. package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +18 -0
  83. package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +18 -0
  84. package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  85. package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +18 -0
  86. package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  87. package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  88. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +18 -0
  89. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +18 -0
  90. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +18 -0
  91. package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +18 -0
  92. package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +18 -0
  93. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +18 -0
  94. package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +18 -0
  95. package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +18 -0
  96. package/dist/runtime/doctrine_trigger_map.json +11 -0
  97. package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +51 -9
  98. package/dist/runtime/hooks/aria-first-class-coach.mjs +129 -0
  99. package/dist/runtime/hooks/aria-harness-via-sdk.mjs +33 -6
  100. package/dist/runtime/hooks/aria-pre-tool-gate.mjs +33 -8
  101. package/dist/runtime/hooks/aria-preprompt-consult.mjs +5 -6
  102. package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +5 -0
  103. package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +15 -0
  104. package/dist/runtime/hooks/aria-stop-gate.mjs +125 -17
  105. package/dist/runtime/hooks/doctrine_trigger_map.json +11 -0
  106. package/dist/runtime/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  107. package/dist/runtime/hooks/lib/emergency-gateoff.mjs +6 -0
  108. package/dist/runtime/hooks/lib/first-class-coach.mjs +755 -0
  109. package/dist/runtime/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  110. package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +1 -14
  111. package/dist/runtime/local-phase.mjs +8 -0
  112. package/dist/runtime/manifest.json +2 -2
  113. package/dist/runtime/provider-proxy.mjs +136 -33
  114. package/dist/runtime/sdk/BUNDLED.json +2 -2
  115. package/dist/runtime/sdk/auth.d.ts +17 -0
  116. package/dist/runtime/sdk/auth.js +158 -0
  117. package/dist/runtime/sdk/auth.js.map +1 -0
  118. package/dist/runtime/sdk/index.d.ts +8 -1
  119. package/dist/runtime/sdk/index.js +15 -1
  120. package/dist/runtime/sdk/index.js.map +1 -1
  121. package/dist/runtime/service.mjs +1711 -74
  122. package/dist/runtime/task-project-ledger.mjs +290 -0
  123. package/dist/sdk/BUNDLED.json +2 -2
  124. package/dist/sdk/auth.d.ts +17 -0
  125. package/dist/sdk/auth.js +158 -0
  126. package/dist/sdk/auth.js.map +1 -0
  127. package/dist/sdk/index.d.ts +8 -1
  128. package/dist/sdk/index.js +15 -1
  129. package/dist/sdk/index.js.map +1 -1
  130. package/hooks/aria-cognition-substrate-binding.mjs +51 -9
  131. package/hooks/aria-first-class-coach.mjs +129 -0
  132. package/hooks/aria-harness-via-sdk.mjs +33 -6
  133. package/hooks/aria-pre-tool-gate.mjs +33 -8
  134. package/hooks/aria-preprompt-consult.mjs +5 -6
  135. package/hooks/aria-preturn-memory-gate.mjs +5 -0
  136. package/hooks/aria-repo-doctrine-gate.mjs +15 -0
  137. package/hooks/aria-stop-gate.mjs +125 -17
  138. package/hooks/doctrine_trigger_map.json +11 -0
  139. package/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  140. package/hooks/lib/emergency-gateoff.mjs +6 -0
  141. package/hooks/lib/first-class-coach.mjs +755 -0
  142. package/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  143. package/hooks/lib/skill-autoload-gate.mjs +1 -14
  144. package/opencode-plugins/harness-context/auth-token.mjs +126 -0
  145. package/opencode-plugins/harness-context/inject-context.mjs +62 -22
  146. package/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  147. package/opencode-plugins/harness-gate/index.js +87 -27
  148. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  149. package/opencode-plugins/harness-outcome/index.js +29 -24
  150. package/opencode-plugins/harness-stop/index.js +229 -68
  151. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  152. package/package.json +8 -2
  153. package/runtime-src/auth-token.mjs +121 -0
  154. package/runtime-src/coach-kernel.mjs +371 -0
  155. package/runtime-src/codex-bridge.mjs +440 -69
  156. package/runtime-src/local-phase.mjs +8 -0
  157. package/runtime-src/provider-proxy.mjs +136 -33
  158. package/runtime-src/service.mjs +1711 -74
  159. package/scripts/bundle-sdk.mjs +8 -0
  160. package/scripts/check-client-compatibility.mjs +422 -0
  161. package/scripts/check-coach-kernel.mjs +204 -0
  162. package/scripts/check-managed-runtime-ledger.mjs +107 -0
  163. package/scripts/check-opencode-config-contract.mjs +78 -0
  164. package/scripts/check-quality-ledger.mjs +121 -0
  165. package/scripts/self-test-harness-gates.mjs +179 -11
  166. package/scripts/self-test-repo-guard.mjs +38 -0
  167. package/scripts/validate-skill-prompts.mjs +14 -1
  168. package/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  169. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  170. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  171. package/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  172. package/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  173. package/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  174. package/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  175. package/skills/aria-cognition/mizan/SKILL.md +18 -0
  176. package/skills/aria-cognition/nadia/SKILL.md +18 -0
  177. package/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  178. package/skills/aria-cognition/predictor/SKILL.md +18 -0
  179. package/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  180. package/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  181. package/src/auth.ts +136 -1
  182. package/src/chat.ts +13 -8
  183. package/src/config.ts +6 -1
  184. package/src/connectors/claude-code.ts +62 -18
  185. package/src/connectors/codex.ts +308 -10
  186. package/src/connectors/opencode.ts +35 -12
  187. package/src/connectors/repo-guard.ts +117 -172
  188. package/src/connectors/runtime.ts +19 -7
  189. package/src/connectors/shell.ts +12 -8
  190. package/src/harness-client.ts +8 -22
  191. package/src/model-context.ts +6 -0
  192. package/src/providers/types.ts +1 -1
  193. package/src/providers/xai.ts +55 -0
  194. package/src/setup-wizard.ts +1 -0
  195. package/src/types.ts +2 -0
@@ -53,6 +53,7 @@ import {
53
53
  normalizeRole,
54
54
  } from './lib/hook-message-window.mjs';
55
55
  import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.mjs';
56
+ import { emergencyGateOffDecision } from './lib/emergency-gateoff.mjs';
56
57
 
57
58
  const HOME = process.env.HOME || '/tmp';
58
59
  const LOG = `${HOME}/.claude/aria-pre-tool-gate.log`;
@@ -72,7 +73,20 @@ function runUniversalGovernanceGate(payload) {
72
73
  try { result = stdout ? JSON.parse(stdout) : null; } catch {}
73
74
  if (child.status !== 0 || result?.ok === false || result?.decision === 'block') {
74
75
  const reason = stdout || child.stderr || 'aria-governance-gate blocked this action.';
75
- throw new Error(`=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===\n\n${reason}`);
76
+ throw new Error([
77
+ '=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===',
78
+ '',
79
+ reason,
80
+ '',
81
+ 'Recovery contract:',
82
+ '1. Do not retry the same blocked action unchanged.',
83
+ '2. Load or apply the doctrine/skill named by the governance output.',
84
+ '3. Re-write the action with <applied_cognition> and concrete proof.',
85
+ '4. Re-test by submitting the revised action through this same gate.',
86
+ ].join('\n'));
87
+ }
88
+ if (result?.decision === 'warn' || result?.governanceMode === 'recovery-required' || result?.governanceMode === 'architectural-intervention-required') {
89
+ process.stderr.write(`[aria-governance:${result.governanceMode || 'recovery-required'}] ${JSON.stringify(result)}\n`);
76
90
  }
77
91
  return result;
78
92
  }
@@ -181,7 +195,7 @@ function audit(decision, summary) {
181
195
  // Bootstrap: if no plan exists, the hook STILL blocks (per Hamza 2026-04-27
182
196
  // "the point is to stop wasting my time"). Claude must wait for Aria to issue
183
197
  // a plan via preprompt-consult on the next user prompt. NO unbound execution.
184
- const BINDING_ENABLED = (process.env.ARIA_BINDING_ENABLED || 'true').toLowerCase() !== 'false';
198
+ const BINDING_ENABLED = true;
185
199
  const BINDING_AUDIT = `${HOME}/.claude/aria-binding-audit.jsonl`;
186
200
 
187
201
  function bindingAuditAppend(record) {
@@ -599,12 +613,12 @@ const SHORT_BASH_LIMIT = 30;
599
613
  // accept flattened generic aliases because they change the lens meaning.
600
614
  const _INLINE_LENS_PATTERN = LENS_NAMES.join('|');
601
615
  const INLINE_LENS_LINE_RX_GLOBAL = new RegExp(
602
- `^\\s*#\\s*(${_INLINE_LENS_PATTERN})\\s*:\\s*(.+)$`,
616
+ `^\\s*(?:#|:\\s*["'])\\s*(${_INLINE_LENS_PATTERN})\\s*:\\s*(.+?)(?:["']\\s*)?$`,
603
617
  'gim',
604
618
  );
605
- const INLINE_COGNITION_HEADER_RX = /^\s*#\s*cognition\s*:\s*(.+)$/im;
606
- const INLINE_EXPECTED_LINE_RX = /^\s*#\s*expected\s*:\s*(.+)$/gim;
607
- const INLINE_VERIFY_LINE_RX = /^\s*#\s*verify\s*:\s*(.+)$/gim;
619
+ const INLINE_COGNITION_HEADER_RX = /^\s*(?:#|:\s*["'])\s*cognition\s*:\s*(.+?)(?:["']\s*)?$/im;
620
+ const INLINE_EXPECTED_LINE_RX = /^\s*(?:#|:\s*["'])\s*expected\s*:\s*(.+?)(?:["']\s*)?$/gim;
621
+ const INLINE_VERIFY_LINE_RX = /^\s*(?:#|:\s*["'])\s*verify\s*:\s*(.+?)(?:["']\s*)?$/gim;
608
622
 
609
623
  const VERIFY_REQUIRED_FIELDS = [
610
624
  { rx: /\btarget\s*:/i, name: 'target' },
@@ -1046,6 +1060,11 @@ try {
1046
1060
  audit('allow-parse-error', 'stdin not JSON');
1047
1061
  process.exit(0); // fail-open on malformed input
1048
1062
  }
1063
+ const emergencyGateOff = emergencyGateOffDecision(event);
1064
+ if (emergencyGateOff.off) {
1065
+ audit('allow-emergency-gateoff', `reason=${emergencyGateOff.reason}`);
1066
+ process.exit(0);
1067
+ }
1049
1068
 
1050
1069
  const toolName = event.tool_name ?? event.toolName ?? '';
1051
1070
  const toolInput = event.tool_input ?? event.toolInput ?? {};
@@ -1317,7 +1336,7 @@ const skillGate = evaluateSkillGate({
1317
1336
  isMutation: toolName !== 'Bash',
1318
1337
  autoLoadAvailable: false,
1319
1338
  });
1320
- if (!skillGate.ok) {
1339
+ if (!skillGate.ok && !skillGate.redirectOnly) {
1321
1340
  const reason = formatSkillGateBlock(skillGate);
1322
1341
  audit('block-missing-skill-receipt', `${skillGate.missingSkills.join(',')} ${cmdPreview}`);
1323
1342
  emitBlock(reason, { source: 'pre-tool/skill-autoload', tool: toolName, lensCount, requiredLenses: REQUIRED_LENSES });
@@ -1403,6 +1422,12 @@ function buildForceRedoActionReason(reasonText, { source = 'pre-tool-gate', tool
1403
1422
  'ORIGINAL GATE REASON:',
1404
1423
  String(reasonText || '').trim(),
1405
1424
  '',
1425
+ 'Recovery contract:',
1426
+ '1. Do not retry the same blocked tool call unchanged.',
1427
+ '2. Apply the teaching above to the next action draft before re-running the tool.',
1428
+ '3. Re-test by submitting the revised action through this same pre-tool gate.',
1429
+ '4. If the blocker is stale state, name the stale state and clear or repair it before retrying.',
1430
+ '',
1406
1431
  'REQUIRED REDO SHAPE:',
1407
1432
  '1. Emit/attach <cognition> with the required lenses before retrying the tool.',
1408
1433
  '2. For Bash, prepend inline cognition comments to the command:',
@@ -2010,7 +2035,7 @@ What Claude must do:
2010
2035
  2. Surface the failure LOUDLY — this is a substrate-level break, not a routine consult miss
2011
2036
  3. Wait for next user prompt — preprompt-consult will retry; if it succeeds a plan will exist on next tool call
2012
2037
 
2013
- Non-trivial actions are blocked until a plan exists. Trivial reads (ls/cat/grep) bypass automatically per existing whitelist. To temporarily disable binding for emergency: ARIA_BINDING_ENABLED=false (logged).`;
2038
+ Non-trivial actions are blocked until a plan exists. Trivial reads (ls/cat/grep) bypass automatically per existing whitelist. Emergency disable requires removing the hook entry or a signed owner override; env-var disable is not honored.`;
2014
2039
  emitBlock(reason, { source: 'pre-tool/substrate-binding' });
2015
2040
  process.exit(2);
2016
2041
  }
@@ -93,16 +93,15 @@ const PACKET_CACHE_PATHS = [
93
93
  `${HOME}/.claude/.aria-harness-last-packet.json`,
94
94
  ];
95
95
  const SUBSTRATE_MANIFEST_PATH = `${HOME}/.claude/.aria-loaded-substrate.json`;
96
- // Default ON. Disable explicitly via ARIA_BINDING_ENABLED=false only when the
97
- // commander/binding architecture is actively being modified (otherwise the
98
- // modification turn itself would be unable to land its own changes). The
99
- // bootstrap path handles the no-plan-yet case by AUTO-ISSUING the first plan
100
- // at hook fire time, so "no plan exists" never becomes "operate unbound."
96
+ // Default ON. Env-var disable is intentionally not honored here; emergency
97
+ // bypass must be an owner-visible hook change, not ambient subprocess drift.
98
+ // The bootstrap path handles the no-plan-yet case by AUTO-ISSUING the first
99
+ // plan at hook fire time, so "no plan exists" never becomes "operate unbound."
101
100
  //
102
101
  // Hamza 2026-04-27: "why would enforcing brick the session? the point is to
103
102
  // stop wasting my time and do quality work." Default-off was convenience-
104
103
  // seeking dressed as responsible-staging. Flipped to default-on per directive.
105
- const BINDING_ENABLED = (process.env.ARIA_BINDING_ENABLED || 'true').toLowerCase() !== 'false';
104
+ const BINDING_ENABLED = true;
106
105
 
107
106
  const HARNESS_URL =
108
107
  process.env.ARIA_HIVE_RUNTIME_URL ||
@@ -72,6 +72,11 @@ function buildForceRedoActionReason({ source, reason, missingSignals = [], incid
72
72
  missingSignals.length ? `\nMISSING SIGNALS:\n${missingSignals.map((signal) => `- ${signal}`).join('\n')}` : '',
73
73
  incidents.length ? `\nINCIDENTS TO RESOLVE:\n${incidents.map((incident) => `- ${incident}`).join('\n')}` : '',
74
74
  '',
75
+ 'Recovery contract:',
76
+ '1. Do not proceed with tools from the blocked context unchanged.',
77
+ '2. Run the structured recovery action in hookSpecificOutput.recovery.',
78
+ '3. Re-test by verifying harness_packet, aria_direction/binding_plan, and memory references are loaded.',
79
+ '',
75
80
  'REQUIRED REDO SHAPE:',
76
81
  '1. Run the structured recovery action in hookSpecificOutput.recovery.',
77
82
  '2. Verify harness_packet, aria_direction/binding_plan, and memory references are loaded.',
@@ -102,6 +102,11 @@ function buildForceRedoActionReason({ source, reason, violations = [] }) {
102
102
  '- External provider calls must use an approved runtime/SDK path or carry the explicit external-call allow marker.',
103
103
  violations.length ? `\nVIOLATIONS TO FIX:\n${violations.map((v) => `- ${v}`).join('\n')}` : '',
104
104
  '',
105
+ 'Recovery contract:',
106
+ '1. Do not retry the same blocked edit/action unchanged.',
107
+ '2. Apply the teaching above by removing or isolating every violating production-path pattern.',
108
+ '3. Re-test by running this repo doctrine gate before retrying the original action.',
109
+ '',
105
110
  'REQUIRED REDO SHAPE:',
106
111
  '1. Preserve the intended behavior without stub/mock/pending semantics.',
107
112
  '2. Use real implementation wiring or an explicit reviewed allow marker.',
@@ -373,6 +378,16 @@ function renderReason(repoRoot, violations, mode) {
373
378
  '',
374
379
  `Allow path categories: tests/specs/fixtures/examples/demos/mocks, or add ${ALLOW_MARKER} in the file when the exception is intentional and reviewable.`,
375
380
  `External provider calls require explicit ${ALLOW_EXTERNAL_LLM_CALL_MARKER} marker or an approved internal runtime/SDK path.`,
381
+ '',
382
+ 'TEACHING:',
383
+ '- Repo doctrine blocks protect production paths from fake or placeholder behavior.',
384
+ '- If this is a real implementation, replace the flagged language with shipped behavior or isolate it under an allowed test/example surface.',
385
+ '',
386
+ 'Recovery contract:',
387
+ '1. Do not retry the same commit/tool action unchanged.',
388
+ '2. Remove or replace each flagged stub/mock/pending/external-call pattern in production paths.',
389
+ `3. If an exception is intentional, add ${ALLOW_MARKER} or ${ALLOW_EXTERNAL_LLM_CALL_MARKER} with a reviewable reason.`,
390
+ '4. Re-run this repo doctrine gate before retrying the original action.',
376
391
  ].join('\n');
377
392
  }
378
393
 
@@ -47,8 +47,9 @@
47
47
  // Future: signed-grant override mechanism at ~/.aria/owner-overrides/<hook>.json
48
48
  // with HMAC signature using a secret only Hamza holds. Deferred to next session.
49
49
 
50
- import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
50
+ import { appendFileSync, readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
51
51
  import { dirname } from 'node:path';
52
+ import { createHash } from 'node:crypto';
52
53
  import { spawnSync } from 'node:child_process';
53
54
  import { appendGateAudit } from './lib/gate-audit.mjs';
54
55
  import {
@@ -61,6 +62,7 @@ import { registerGateBlock } from './lib/gate-loop-state.mjs';
61
62
  import { collectTurnWindowFromMessages } from './lib/hook-message-window.mjs';
62
63
  import { analyzeDomainOutputQuality } from './lib/domain-output-quality.mjs';
63
64
  import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.mjs';
65
+ import { emergencyGateOffDecision } from './lib/emergency-gateoff.mjs';
64
66
 
65
67
  const HOME = process.env.HOME || '/tmp';
66
68
  const RUNTIME_BASE_URL =
@@ -71,6 +73,15 @@ const AUDIT_PATH = `${HOME}/.claude/aria-stop-gate-audit.jsonl`;
71
73
  const GATE_LOOP_STATE_PATH = `${HOME}/.claude/.aria-gate-loop-state.json`;
72
74
  const MIZAN_RECEIPT_DIR = `${HOME}/.claude/.aria-mizan-receipts`;
73
75
  const GOVERNANCE_GATE_PATH = `${HOME}/.aria/bin/aria-governance-gate`;
76
+ const CURRENT_RECOVERY_PATH = `${HOME}/.aria/governance-recovery-current.json`;
77
+ const HANDOFF_MODE_RX = /\b(?:post[_ -]?compact[_ -]?continuation|handoff[_ -]?resume|continuation[_ -]?handoff|post-compact|compact(?:ion)? handoff|post\s+commission)\b/i;
78
+ const HANDOFF_REQUIRED_FIELDS = [
79
+ { label: 'current objective', rx: /\bcurrent\s+objective\b\s*:/i },
80
+ { label: 'known blockers', rx: /\bknown\s+blockers\b\s*:/i },
81
+ { label: 'next executable step', rx: /\bnext\s+executable\s+step\b\s*:/i },
82
+ { label: 'what not to touch', rx: /\bwhat\s+not\s+to\s+touch\b\s*:/i },
83
+ { label: 'verification already run', rx: /\bverification\s+already\s+run\b\s*:/i },
84
+ ];
74
85
 
75
86
  function runUniversalGovernanceGate(payload) {
76
87
  if (!existsSync(GOVERNANCE_GATE_PATH)) return null;
@@ -84,11 +95,49 @@ function runUniversalGovernanceGate(payload) {
84
95
  try { result = stdout ? JSON.parse(stdout) : null; } catch {}
85
96
  if (child.status !== 0 || result?.ok === false || result?.decision === 'block') {
86
97
  const reason = stdout || child.stderr || 'aria-governance-gate blocked this output.';
87
- throw new Error(`=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===\n\n${reason}`);
98
+ throw new Error([
99
+ '=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===',
100
+ '',
101
+ reason,
102
+ '',
103
+ 'Recovery contract:',
104
+ '1. Do not retry the same blocked output unchanged.',
105
+ '2. Load or apply the doctrine/skill named by the governance output.',
106
+ '3. Re-write the output with <applied_cognition> and concrete proof.',
107
+ '4. Re-test by submitting the revised output through this same gate.',
108
+ ].join('\n'));
109
+ }
110
+ if (result?.decision === 'warn' || result?.governanceMode === 'recovery-required' || result?.governanceMode === 'architectural-intervention-required') {
111
+ process.stderr.write(`[aria-governance:${result.governanceMode || 'recovery-required'}] ${JSON.stringify(result)}\n`);
88
112
  }
89
113
  return result;
90
114
  }
91
115
 
116
+ function classifyContinuationHandoff(text) {
117
+ const body = String(text || '');
118
+ const isContinuation = HANDOFF_MODE_RX.test(body);
119
+ const missingFields = HANDOFF_REQUIRED_FIELDS.filter((field) => !field.rx.test(body)).map((field) => field.label);
120
+ return { isContinuation, ok: isContinuation && missingFields.length === 0, missingFields };
121
+ }
122
+
123
+ function formatHandoffRecovery(missingFields = []) {
124
+ return [
125
+ '=== ARIA LOCAL OUTPUT BLOCK ===',
126
+ '',
127
+ `post-compact continuation handoff malformed: missing fields: ${missingFields.join(', ') || '(none)'}`,
128
+ '',
129
+ 'Recovery contract:',
130
+ '1. Do not retry the same blocked text.',
131
+ '2. Rewrite the output using this exact shape:',
132
+ 'post_compact_continuation',
133
+ 'current objective: <one sentence of the still-active task>',
134
+ 'known blockers: <specific blockers, or "none verified" if truly none>',
135
+ 'next executable step: <single command/edit/read/action the next agent should run first>',
136
+ 'what not to touch: <unrelated dirty files, secrets, protected systems, or deploys to avoid>',
137
+ 'verification already run: <commands/probes already run and exact result, or "not run">',
138
+ ].join('\n');
139
+ }
140
+
92
141
  // SDK loader — bundled at ~/.aria/sdk by `aria connect`, with client-local
93
142
  // fallbacks preserved for resilience.
94
143
  // All control-plane fetches (validateOutput, gardenTurn) route through the
@@ -442,6 +491,12 @@ function buildForceReauthorReason({
442
491
  rewritten ? `\nMIZAN REWRITE SEED:\n${rewritten}` : '',
443
492
  recipeAddendum || '',
444
493
  '',
494
+ 'Recovery contract:',
495
+ '1. Do not retry the same blocked text unchanged.',
496
+ '2. Apply the teaching above to the next draft before re-emitting.',
497
+ '3. Re-test by submitting the revised output through this same gate.',
498
+ '4. If the blocker is stale state, name the stale state and clear or repair it before retrying.',
499
+ '',
445
500
  'REQUIRED REDO SHAPE:',
446
501
  '1. One sentence: the failed mechanism, not an apology.',
447
502
  '2. Evidence checked: file/line, command output, endpoint response, or explicit "unverified".',
@@ -472,6 +527,50 @@ function buildForceReauthorReason({
472
527
  ].filter(Boolean).join('\n');
473
528
  }
474
529
 
530
+ function recoveryFingerprint(reason, source = 'stop-gate') {
531
+ return createHash('sha256')
532
+ .update([source, String(reason || '').replace(/\s+/g, ' ').trim().slice(0, 1200)].join('\0'))
533
+ .digest('hex');
534
+ }
535
+
536
+ function emitRecoverableStopGate(reason, { source = 'stop-gate', sessionId = gateSessionId } = {}) {
537
+ const fingerprint = recoveryFingerprint(reason, source);
538
+ const recovery = {
539
+ schema: 'aria.governance_recovery_current.v1',
540
+ updatedAt: new Date().toISOString(),
541
+ deliveryRule: 'This file is injected into the next system prompt. Execute recoveryLoop.nextStep before any completion claim.',
542
+ ok: true,
543
+ decision: 'warn',
544
+ source: 'aria-stop-gate',
545
+ governanceMode: 'recovery-required',
546
+ recoveryLoop: {
547
+ fingerprint,
548
+ allowedRecoveryAttempts: 1,
549
+ priorRecoveryAttempts: 0,
550
+ remainingRecoveryAttempts: 1,
551
+ nextStep: 'Re-author the blocked output using the recovery contract, load required skills if named, run a concrete verification probe when tools are available, then emit the corrected output.',
552
+ architectFallback: 'If Aria consult is unavailable or this same fingerprint repeats, use the strongest available client LLM under the architect harness, produce and execute the recovery plan, then verify.',
553
+ },
554
+ recoveryContract: {
555
+ retry: 'One recovery attempt is allowed for this exact output-gate fingerprint; repeat failure enters architect execution mode.',
556
+ architectMode: 'On repeat, load architecture-decision and the named domain skills, define the smallest executable plan, execute the first recovery action, and verify before output.',
557
+ fallbackWhenAriaUnavailable: 'Do not stop or ask the owner to fix it. Bind the strongest available client LLM to the architect harness, give it this JSON contract and failed evidence, execute its recovery plan, then verify.',
558
+ repairRecoveryCycle: ['re-author output according to the gate reason', 'verify the corrected claim or state explicit unverified boundary'],
559
+ retest: 'Submit the revised output through the same stop gate and run the concrete verification probe named by the recovery plan.',
560
+ blockedReason: reason,
561
+ },
562
+ };
563
+ try {
564
+ mkdirSync(dirname(CURRENT_RECOVERY_PATH), { recursive: true, mode: 0o700 });
565
+ writeFileSync(CURRENT_RECOVERY_PATH, `${JSON.stringify(recovery, null, 2)}\n`, { mode: 0o600 });
566
+ } catch (error) {
567
+ audit('recovery-state-write-failed', error instanceof Error ? error.message : String(error));
568
+ }
569
+ audit('recovery-required-output-gate', `${source} ${fingerprint}`);
570
+ console.log(JSON.stringify({ decision: 'allow', recoveryRequired: recovery }));
571
+ process.exit(0);
572
+ }
573
+
475
574
  // Lens substance check — same constants as aria-pre-tool-gate.mjs.
476
575
  // Hamza directive 2026-04-28: all 8 canonical lenses required, not 4-of-8.
477
576
  const REQUIRED_LENSES = 8;
@@ -517,6 +616,11 @@ try {
517
616
  audit('allow-parse-error', 'stdin not JSON');
518
617
  process.exit(0);
519
618
  }
619
+ const emergencyGateOff = emergencyGateOffDecision(event);
620
+ if (emergencyGateOff.off) {
621
+ audit('allow-emergency-gateoff', `reason=${emergencyGateOff.reason}`);
622
+ process.exit(0);
623
+ }
520
624
  const gateSessionId = String(event.session_id || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
521
625
  const sessionMizanState = loadMizanReceiptState(event.session_id || 'claude-code');
522
626
 
@@ -616,8 +720,7 @@ if (userCorrectionViolation) {
616
720
  lensCount: 0,
617
721
  requiredLenses: REQUIRED_LENSES,
618
722
  });
619
- console.log(JSON.stringify({ decision: 'block', reason }));
620
- process.exit(2);
723
+ emitRecoverableStopGate(reason, { source: 'stop/user-correction', sessionId: gateSessionId });
621
724
  }
622
725
 
623
726
  // Triviality check — same as eight-lens-detector.ts
@@ -640,6 +743,17 @@ if (!triggered) {
640
743
  process.exit(0);
641
744
  }
642
745
 
746
+ const handoff = classifyContinuationHandoff(assistantText);
747
+ if (handoff.isContinuation) {
748
+ if (handoff.ok) {
749
+ audit('allow-post-compact-continuation', `chars=${assistantText.length}`);
750
+ await fireGardenTurn(event.session_id || 'claude-code', lastUserMessage, assistantText);
751
+ process.exit(0);
752
+ }
753
+ audit('block-post-compact-continuation', `missing=${handoff.missingFields.join(',')}`);
754
+ emitRecoverableStopGate(formatHandoffRecovery(handoff.missingFields), { source: 'stop/post-compact-continuation', sessionId: gateSessionId });
755
+ }
756
+
643
757
  const stopSkillGate = evaluateSkillGate({
644
758
  sessionId: event.session_id || 'claude-code',
645
759
  surface: 'claude-stop-gate',
@@ -647,7 +761,7 @@ const stopSkillGate = evaluateSkillGate({
647
761
  isOutputCloseout: true,
648
762
  autoLoadAvailable: false,
649
763
  });
650
- if (!stopSkillGate.ok) {
764
+ if (!stopSkillGate.ok && !stopSkillGate.redirectOnly) {
651
765
  audit('block-missing-skill-receipt', `missing=${stopSkillGate.missingSkills.join(',')} chars=${assistantText.length}`);
652
766
  const reason = withLoopDirective(buildForceReauthorReason({
653
767
  source: 'stop/skill-autoload',
@@ -656,8 +770,7 @@ if (!stopSkillGate.ok) {
656
770
  lensCount: 0,
657
771
  requiredLenses: REQUIRED_LENSES,
658
772
  }), `stop:skill-autoload:${stopSkillGate.missingSkills.join(',')}`, gateSessionId);
659
- console.log(JSON.stringify({ decision: 'block', reason }));
660
- process.exit(2);
773
+ emitRecoverableStopGate(reason, { source: 'stop/skill-autoload', sessionId: gateSessionId });
661
774
  }
662
775
  try {
663
776
  runUniversalGovernanceGate({
@@ -678,8 +791,7 @@ try {
678
791
  lensCount: 0,
679
792
  requiredLenses: REQUIRED_LENSES,
680
793
  }), 'stop:universal-governance', gateSessionId);
681
- console.log(JSON.stringify({ decision: 'block', reason }));
682
- process.exit(2);
794
+ emitRecoverableStopGate(reason, { source: 'stop/universal-governance', sessionId: gateSessionId });
683
795
  }
684
796
 
685
797
  // Non-trivial response — require substantive cognition.
@@ -712,8 +824,7 @@ if (cog.count < REQUIRED_LENSES) {
712
824
  codeCount: 0,
713
825
  implCouplingCount: 0,
714
826
  });
715
- console.log(JSON.stringify({ decision: 'block', reason }));
716
- process.exit(2);
827
+ emitRecoverableStopGate(reason, { source: 'stop/no-cognition', sessionId: gateSessionId });
717
828
  }
718
829
 
719
830
  // Question-emission visibility (Phase 11 promotes to block-mode):
@@ -1596,8 +1707,7 @@ if (cog.count >= REQUIRED_LENSES) {
1596
1707
  codeCount: codeQualityHits.length,
1597
1708
  implCouplingCount: implCouplingHits.length,
1598
1709
  });
1599
- console.log(JSON.stringify({ decision: 'block', reason }));
1600
- process.exit(2);
1710
+ emitRecoverableStopGate(reason, { source: 'stop/output-quality', sessionId: gateSessionId });
1601
1711
  }
1602
1712
 
1603
1713
  audit('allow-output-qc',
@@ -1752,8 +1862,7 @@ if (hadNonTrivialAction && (!dalioExpectedMatch || !dalioHasMeasurablePredicate)
1752
1862
  codeCount: 0,
1753
1863
  implCouplingCount: 0,
1754
1864
  });
1755
- console.log(JSON.stringify({ decision: 'block', reason: missingReason }));
1756
- process.exit(2);
1865
+ emitRecoverableStopGate(missingReason, { source: 'stop/dalio-expected', sessionId: gateSessionId });
1757
1866
  }
1758
1867
 
1759
1868
  // Dalio ledger write — fire-and-forget HTTP POST + local JSONL mirror.
@@ -1878,5 +1987,4 @@ emitHarnessFooter({
1878
1987
  codeCount: 0,
1879
1988
  implCouplingCount: 0,
1880
1989
  });
1881
- console.log(JSON.stringify({ decision: 'block', reason: buildForceReauthorReason({ source: 'stop/lens-missing', reason, lensCount: cog.count, requiredLenses: REQUIRED_LENSES }) }));
1882
- process.exit(2);
1990
+ emitRecoverableStopGate(buildForceReauthorReason({ source: 'stop/lens-missing', reason, lensCount: cog.count, requiredLenses: REQUIRED_LENSES }), { source: 'stop/lens-missing', sessionId: gateSessionId });
@@ -572,6 +572,17 @@
572
572
  "teaching": "A service without a K8s manifest is not deployed — it's aspirational code. Every service in the registry must have a canonical k8s/<service>.yaml manifest. Without it, kubectl apply cannot restore the service after cluster restart.",
573
573
  "counter_action": "Create the canonical K8s manifest (Deployment + Service + liveness probe) at k8s/<service>.yaml. Register the service in k8s/service-registry.json. Never deploy via ad-hoc kubectl run or docker run — only via a version-controlled manifest.",
574
574
  "message": "Service lacks K8s manifest — see feedback_registry_image_drift.md"
575
+ },
576
+ {
577
+ "trigger_id": "containment_only_production_fix",
578
+ "trigger": "\\b(?:containment[- ]?only|only[- ]?contain(?:ed|ment)|just[- ]?contain(?:ed|ment))\\b|\\b(?:surface|cosmetic|appearance[- ]?only|form[- ]?only)\\s+(?:fix|repair|change)\\b|\\b(?:fix|repair|change)\\s+(?:only\\s+)?(?:the\\s+)?(?:surface|cosmetic|appearance|form)\\b|\\b(?:just|only|merely)\\s+(?:silence|satisfy|appease)\\s+(?:the\\s+)?(?:gate|hook|validator)\\b",
579
+ "rx": "\\b(?:containment[- ]?only|only[- ]?contain(?:ed|ment)|just[- ]?contain(?:ed|ment))\\b|\\b(?:surface|cosmetic|appearance[- ]?only|form[- ]?only)\\s+(?:fix|repair|change)\\b|\\b(?:fix|repair|change)\\s+(?:only\\s+)?(?:the\\s+)?(?:surface|cosmetic|appearance|form)\\b|\\b(?:just|only|merely)\\s+(?:silence|satisfy|appease)\\s+(?:the\\s+)?(?:gate|hook|validator)\\b",
580
+ "doctrine": "memory:feedback_gates_enforce_form_not_substance.md",
581
+ "memory": "feedback_gates_enforce_form_not_substance.md",
582
+ "severity": "block",
583
+ "teaching": "Containment-only production fixes are rationalized bypasses when they intentionally leave the broken user-visible mechanism unrepaired. Small source-level fixes are acceptable when they repair the root mechanism and are verified.",
584
+ "counter_action": "Keep the smallest correct change, but tie it to the root mechanism, observable recovery or proof, and user-visible verification; do not only silence gates or change cosmetics.",
585
+ "message": "Containment-only production fix detected. Preserve smallest-correct-change discipline, but replace cosmetic containment with root-mechanism repair plus observable recovery and proof."
575
586
  }
576
587
  ]
577
588
  }
@@ -0,0 +1,39 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ const GATEOFF_ENV_RX = /^(?:1|true|yes|on)$/i;
6
+ const GATEOFF_PHRASE_RX = /\bGATEOFF\b/;
7
+
8
+ export const gateOffMarkerPath = join(homedir(), '.aria', 'gates-off.manual');
9
+
10
+ function textFromPayload(payload) {
11
+ if (!payload) return '';
12
+ if (typeof payload === 'string') return payload;
13
+ try { return JSON.stringify(payload); } catch { return String(payload); }
14
+ }
15
+
16
+ export function persistEmergencyGateOff(reason = 'phrase') {
17
+ mkdirSync(dirname(gateOffMarkerPath), { recursive: true, mode: 0o700 });
18
+ writeFileSync(gateOffMarkerPath, JSON.stringify({
19
+ disabled: true,
20
+ reason,
21
+ disabledAt: new Date().toISOString(),
22
+ reenable: 'Manual owner action only: remove this marker and unset ARIA_GATES_OFF/ARIA_HARNESS_GATES_OFF.',
23
+ }, null, 2) + '\n', { mode: 0o600 });
24
+ }
25
+
26
+ export function detectEmergencyGateOff(payload = '') {
27
+ if (GATEOFF_ENV_RX.test(String(process.env.ARIA_GATES_OFF || process.env.ARIA_HARNESS_GATES_OFF || ''))) {
28
+ return { off: true, reason: 'env' };
29
+ }
30
+ if (existsSync(gateOffMarkerPath)) return { off: true, reason: 'marker' };
31
+ if (GATEOFF_PHRASE_RX.test(textFromPayload(payload))) return { off: true, reason: 'phrase' };
32
+ return { off: false, reason: null };
33
+ }
34
+
35
+ export function emergencyGateOffDecision(payload = '') {
36
+ const decision = detectEmergencyGateOff(payload);
37
+ if (decision.off && decision.reason === 'phrase') persistEmergencyGateOff('phrase');
38
+ return decision;
39
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ detectEmergencyGateOff,
3
+ emergencyGateOffDecision,
4
+ gateOffMarkerPath,
5
+ persistEmergencyGateOff,
6
+ } from './emergency-gateoff-impl.mjs';