@aria_asi/cli 0.2.25 → 0.2.29

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 (249) hide show
  1. package/CLIENT-ONBOARDING.md +282 -0
  2. package/bin/aria.js +1140 -14
  3. package/dist/aria-connector/src/auth-commands.d.ts +1 -0
  4. package/dist/aria-connector/src/auth-commands.d.ts.map +1 -1
  5. package/dist/aria-connector/src/auth-commands.js +89 -41
  6. package/dist/aria-connector/src/auth-commands.js.map +1 -1
  7. package/dist/aria-connector/src/chat.d.ts +3 -0
  8. package/dist/aria-connector/src/chat.d.ts.map +1 -1
  9. package/dist/aria-connector/src/chat.js +146 -8
  10. package/dist/aria-connector/src/chat.js.map +1 -1
  11. package/dist/aria-connector/src/codebase-scanner.d.ts +2 -2
  12. package/dist/aria-connector/src/codebase-scanner.d.ts.map +1 -1
  13. package/dist/aria-connector/src/codebase-scanner.js +1 -1
  14. package/dist/aria-connector/src/codebase-scanner.js.map +1 -1
  15. package/dist/aria-connector/src/config.d.ts +12 -0
  16. package/dist/aria-connector/src/config.d.ts.map +1 -1
  17. package/dist/aria-connector/src/config.js +2 -0
  18. package/dist/aria-connector/src/config.js.map +1 -1
  19. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  20. package/dist/aria-connector/src/connectors/claude-code.js +111 -21
  21. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  22. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +37 -0
  23. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -0
  24. package/dist/aria-connector/src/connectors/codebase-awareness.js +335 -0
  25. package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -0
  26. package/dist/aria-connector/src/connectors/codex.d.ts +3 -0
  27. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -0
  28. package/dist/aria-connector/src/connectors/codex.js +248 -0
  29. package/dist/aria-connector/src/connectors/codex.js.map +1 -0
  30. package/dist/aria-connector/src/connectors/cognitive-skills.d.ts +2 -0
  31. package/dist/aria-connector/src/connectors/cognitive-skills.d.ts.map +1 -0
  32. package/dist/aria-connector/src/connectors/cognitive-skills.js +47 -0
  33. package/dist/aria-connector/src/connectors/cognitive-skills.js.map +1 -0
  34. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  35. package/dist/aria-connector/src/connectors/opencode.js +90 -4
  36. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  37. package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts +3 -0
  38. package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts.map +1 -0
  39. package/dist/aria-connector/src/connectors/repo-git-hooks.js +87 -0
  40. package/dist/aria-connector/src/connectors/repo-git-hooks.js.map +1 -0
  41. package/dist/aria-connector/src/connectors/repo-guard.d.ts +19 -0
  42. package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -0
  43. package/dist/aria-connector/src/connectors/repo-guard.js +509 -0
  44. package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -0
  45. package/dist/aria-connector/src/connectors/runtime.d.ts +2 -0
  46. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -0
  47. package/dist/aria-connector/src/connectors/runtime.js +330 -0
  48. package/dist/aria-connector/src/connectors/runtime.js.map +1 -0
  49. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  50. package/dist/aria-connector/src/connectors/shell.js +78 -13
  51. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  52. package/dist/aria-connector/src/connectors/syncd.d.ts +27 -0
  53. package/dist/aria-connector/src/connectors/syncd.d.ts.map +1 -0
  54. package/dist/aria-connector/src/connectors/syncd.js +405 -0
  55. package/dist/aria-connector/src/connectors/syncd.js.map +1 -0
  56. package/dist/aria-connector/src/decisions.d.ts +207 -0
  57. package/dist/aria-connector/src/decisions.d.ts.map +1 -0
  58. package/dist/aria-connector/src/decisions.js +291 -0
  59. package/dist/aria-connector/src/decisions.js.map +1 -0
  60. package/dist/aria-connector/src/garden-control-plane.d.ts.map +1 -1
  61. package/dist/aria-connector/src/garden-control-plane.js +74 -17
  62. package/dist/aria-connector/src/garden-control-plane.js.map +1 -1
  63. package/dist/aria-connector/src/github-connect.d.ts +18 -0
  64. package/dist/aria-connector/src/github-connect.d.ts.map +1 -0
  65. package/dist/aria-connector/src/github-connect.js +117 -0
  66. package/dist/aria-connector/src/github-connect.js.map +1 -0
  67. package/dist/aria-connector/src/harness-client.d.ts +15 -0
  68. package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
  69. package/dist/aria-connector/src/harness-client.js +106 -3
  70. package/dist/aria-connector/src/harness-client.js.map +1 -1
  71. package/dist/aria-connector/src/hive-client.d.ts +30 -0
  72. package/dist/aria-connector/src/hive-client.d.ts.map +1 -1
  73. package/dist/aria-connector/src/hive-client.js +124 -5
  74. package/dist/aria-connector/src/hive-client.js.map +1 -1
  75. package/dist/aria-connector/src/index.d.ts +13 -2
  76. package/dist/aria-connector/src/index.d.ts.map +1 -1
  77. package/dist/aria-connector/src/index.js +10 -1
  78. package/dist/aria-connector/src/index.js.map +1 -1
  79. package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts +102 -0
  80. package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts.map +1 -0
  81. package/dist/aria-connector/src/lib/aristotle-noor-wire.js +231 -0
  82. package/dist/aria-connector/src/lib/aristotle-noor-wire.js.map +1 -0
  83. package/dist/aria-connector/src/providers/types.d.ts +5 -0
  84. package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
  85. package/dist/aria-connector/src/runtime-proof.d.ts +45 -0
  86. package/dist/aria-connector/src/runtime-proof.d.ts.map +1 -0
  87. package/dist/aria-connector/src/runtime-proof.js +340 -0
  88. package/dist/aria-connector/src/runtime-proof.js.map +1 -0
  89. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
  90. package/dist/aria-connector/src/setup-wizard.js +34 -2
  91. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  92. package/dist/assets/hooks/aria-agent-handoff.mjs +224 -0
  93. package/dist/assets/hooks/aria-agent-ledger-merge.mjs +164 -0
  94. package/dist/assets/hooks/aria-architect-fallback.mjs +267 -0
  95. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +676 -0
  96. package/dist/assets/hooks/aria-discovery-record.mjs +101 -0
  97. package/dist/assets/hooks/aria-harness-via-sdk.mjs +412 -0
  98. package/dist/assets/hooks/aria-import-resolution-gate.mjs +330 -0
  99. package/dist/assets/hooks/aria-outcome-record.mjs +84 -0
  100. package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +294 -0
  101. package/dist/assets/hooks/aria-pre-text-gate.mjs +112 -0
  102. package/dist/assets/hooks/aria-pre-tool-gate.mjs +2133 -0
  103. package/dist/assets/hooks/aria-preprompt-consult.mjs +438 -0
  104. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +570 -0
  105. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +397 -0
  106. package/dist/assets/hooks/aria-stop-gate.mjs +1551 -0
  107. package/dist/assets/hooks/aria-trigger-autolearn.mjs +229 -0
  108. package/dist/assets/hooks/aria-userprompt-abandon-detect.mjs +192 -0
  109. package/dist/assets/hooks/doctrine_trigger_map.json +479 -0
  110. package/dist/assets/hooks/lib/canonical-lenses.mjs +64 -0
  111. package/dist/assets/hooks/lib/gate-audit.mjs +43 -0
  112. package/dist/assets/hooks/test-aria-preturn-memory-gate.mjs +245 -0
  113. package/dist/assets/hooks/test-tier-lens-labeling.mjs +399 -0
  114. package/dist/assets/opencode-plugins/harness-context/index.js +60 -0
  115. package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +179 -0
  116. package/dist/assets/opencode-plugins/harness-context/package.json +9 -0
  117. package/dist/assets/opencode-plugins/harness-gate/index.js +248 -0
  118. package/dist/assets/opencode-plugins/harness-outcome/index.js +129 -0
  119. package/dist/assets/opencode-plugins/harness-role/index.js +77 -0
  120. package/dist/assets/opencode-plugins/harness-role/package.json +9 -0
  121. package/dist/assets/opencode-plugins/harness-stop/index.js +241 -0
  122. package/dist/runtime/discipline/CLAUDE.md +339 -0
  123. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +63 -0
  124. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
  125. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
  126. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
  127. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
  128. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
  129. package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
  130. package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
  131. package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
  132. package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
  133. package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
  134. package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +72 -0
  135. package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +38 -0
  136. package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
  137. package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +25 -0
  138. package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
  139. package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +25 -0
  140. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +81 -0
  141. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +98 -0
  142. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +99 -0
  143. package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +127 -0
  144. package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +117 -0
  145. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +112 -0
  146. package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +102 -0
  147. package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +121 -0
  148. package/dist/runtime/doctor.mjs +23 -0
  149. package/dist/runtime/local-phase.mjs +632 -0
  150. package/dist/runtime/manifest.json +15 -0
  151. package/dist/runtime/mizan-scheduler.mjs +331 -0
  152. package/dist/runtime/package.json +6 -0
  153. package/dist/runtime/provider-proxy.mjs +594 -0
  154. package/dist/runtime/sdk/BUNDLED.json +5 -0
  155. package/dist/runtime/sdk/index.d.ts +477 -0
  156. package/dist/runtime/sdk/index.js +1469 -0
  157. package/dist/runtime/sdk/index.js.map +1 -0
  158. package/dist/runtime/sdk/package.json +8 -0
  159. package/dist/runtime/sdk/runWithCognition.d.ts +77 -0
  160. package/dist/runtime/sdk/runWithCognition.js +157 -0
  161. package/dist/runtime/sdk/runWithCognition.js.map +1 -0
  162. package/dist/runtime/service.mjs +2708 -0
  163. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +53 -0
  164. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -0
  165. package/dist/runtime/vendor/aria-gate-runtime/index.js +277 -0
  166. package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -0
  167. package/dist/runtime/vendor/aria-gate-runtime/package.json +6 -0
  168. package/dist/sdk/BUNDLED.json +2 -2
  169. package/dist/sdk/index.d.ts +317 -0
  170. package/dist/sdk/index.js +827 -85
  171. package/dist/sdk/index.js.map +1 -1
  172. package/dist/sdk/runWithCognition.d.ts +77 -0
  173. package/dist/sdk/runWithCognition.js +157 -0
  174. package/dist/sdk/runWithCognition.js.map +1 -0
  175. package/hooks/aria-agent-handoff.mjs +11 -1
  176. package/hooks/aria-architect-fallback.mjs +267 -0
  177. package/hooks/aria-cognition-substrate-binding.mjs +676 -0
  178. package/hooks/aria-discovery-record.mjs +101 -0
  179. package/hooks/aria-harness-via-sdk.mjs +34 -21
  180. package/hooks/aria-import-resolution-gate.mjs +330 -0
  181. package/hooks/aria-outcome-record.mjs +84 -0
  182. package/hooks/aria-pre-emit-dryrun.mjs +294 -0
  183. package/hooks/aria-pre-tool-gate.mjs +985 -40
  184. package/hooks/aria-preprompt-consult.mjs +113 -13
  185. package/hooks/aria-preturn-memory-gate.mjs +298 -6
  186. package/hooks/aria-repo-doctrine-gate.mjs +397 -0
  187. package/hooks/aria-stop-gate.mjs +840 -75
  188. package/hooks/aria-userprompt-abandon-detect.mjs +5 -1
  189. package/hooks/doctrine_trigger_map.json +209 -15
  190. package/hooks/lib/canonical-lenses.mjs +64 -0
  191. package/hooks/lib/gate-audit.mjs +43 -0
  192. package/opencode-plugins/harness-context/index.js +1 -1
  193. package/opencode-plugins/harness-context/inject-context.mjs +82 -23
  194. package/opencode-plugins/harness-gate/index.js +248 -0
  195. package/opencode-plugins/harness-outcome/index.js +129 -0
  196. package/opencode-plugins/harness-stop/index.js +241 -0
  197. package/package.json +8 -2
  198. package/runtime-src/doctor.mjs +23 -0
  199. package/runtime-src/local-phase.mjs +632 -0
  200. package/runtime-src/mizan-scheduler.mjs +331 -0
  201. package/runtime-src/provider-proxy.mjs +594 -0
  202. package/runtime-src/service.mjs +2708 -0
  203. package/scripts/bundle-sdk.mjs +317 -0
  204. package/scripts/install-client.sh +176 -0
  205. package/scripts/publish-all.sh +344 -0
  206. package/scripts/publish-docker.sh +27 -0
  207. package/scripts/validate-hook-contracts.mjs +54 -0
  208. package/scripts/validate-skill-prompts.mjs +95 -0
  209. package/skills/aria-cognition/aria-essence/SKILL.md +63 -0
  210. package/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
  211. package/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
  212. package/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
  213. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
  214. package/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
  215. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
  216. package/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
  217. package/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
  218. package/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
  219. package/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
  220. package/skills/aria-cognition/mizan/SKILL.md +72 -0
  221. package/skills/aria-cognition/nadia/SKILL.md +38 -0
  222. package/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
  223. package/skills/aria-cognition/predictor/SKILL.md +25 -0
  224. package/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
  225. package/skills/aria-cognition/soul-domains/SKILL.md +25 -0
  226. package/src/auth-commands.ts +111 -45
  227. package/src/chat.ts +174 -13
  228. package/src/codebase-scanner.ts +4 -0
  229. package/src/config.ts +15 -0
  230. package/src/connectors/claude-code.ts +115 -26
  231. package/src/connectors/codebase-awareness.ts +408 -0
  232. package/src/connectors/codex.ts +274 -0
  233. package/src/connectors/cognitive-skills.ts +51 -0
  234. package/src/connectors/opencode.ts +93 -4
  235. package/src/connectors/repo-git-hooks.ts +86 -0
  236. package/src/connectors/repo-guard.ts +589 -0
  237. package/src/connectors/runtime.ts +374 -0
  238. package/src/connectors/shell.ts +83 -14
  239. package/src/connectors/syncd.ts +488 -0
  240. package/src/decisions.ts +469 -0
  241. package/src/garden-control-plane.ts +101 -26
  242. package/src/github-connect.ts +143 -0
  243. package/src/harness-client.ts +128 -3
  244. package/src/hive-client.ts +165 -5
  245. package/src/index.ts +41 -2
  246. package/src/lib/aristotle-noor-wire.ts +310 -0
  247. package/src/providers/types.ts +6 -0
  248. package/src/runtime-proof.ts +392 -0
  249. package/src/setup-wizard.ts +37 -2
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ // aria-trigger-autolearn.mjs — UserPromptSubmit hook that scans Hamza's
3
+ // (or any user's) corrections for novel doctrine-violation patterns and
4
+ // queues them as candidate trigger entries for doctrine_trigger_map.json.
5
+ //
6
+ // Doctrine: Aria enforcement #47 (drift-guard auto-learning queue) +
7
+ // feedback_no_flag_without_fix.md + project_phase_10_endless_army_orchestration.md
8
+ // (the harness teaches itself by absorbing corrections, not staying frozen
9
+ // at hand-curated rules).
10
+ //
11
+ // Mechanics: when a user prompt contains correction/doctrine-language
12
+ // (e.g. "don't ___", "stop ___ing", "no ___", "we said ___", "doctrine ___"),
13
+ // the hook extracts the candidate pattern + a context window from the
14
+ // recent assistant transcript (the offending behavior the user is correcting)
15
+ // and appends a JSONL entry to ~/.claude/aria-trigger-queue.jsonl.
16
+ //
17
+ // The queue is reviewable via `cat ~/.claude/aria-trigger-queue.jsonl` or
18
+ // (Phase 11) a future `aria triggers review` CLI subcommand. Each entry
19
+ // carries enough context that a human can decide:
20
+ // 1. Add to doctrine_trigger_map.json as a new trigger
21
+ // 2. Refine an existing trigger entry's regex
22
+ // 3. Discard (false positive — user correction wasn't a doctrine teaching)
23
+ //
24
+ // Hook is non-blocking: never returns decision=block. Failure modes degrade
25
+ // to silent skip (queue file unwritable = no auto-learn, but session
26
+ // continues). This is the ONE permitted graceful path because the hook is
27
+ // purely additive — its absence doesn't break correctness, just slows
28
+ // learning.
29
+ //
30
+ // No env-var kill-switch (Hamza 2026-04-27 — env-var disable paths gave
31
+ // the gated process free escape; doctrine violation). Disable = remove
32
+ // hook entry from ~/.claude/settings.json.
33
+
34
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
35
+ import { dirname } from 'node:path';
36
+
37
+ const HOME = process.env.HOME || '/tmp';
38
+ const LOG = `${HOME}/.claude/aria-autolearn.log`;
39
+ const QUEUE = `${HOME}/.claude/aria-trigger-queue.jsonl`;
40
+
41
+ function audit(decision, summary) {
42
+ try {
43
+ if (!existsSync(dirname(LOG))) mkdirSync(dirname(LOG), { recursive: true });
44
+ appendFileSync(LOG, `${new Date().toISOString()} ${decision} ${summary}\n`);
45
+ } catch {}
46
+ }
47
+
48
+ // Kill-switch
49
+ // Env-var kill-switch removed 2026-04-27 per Hamza directive.
50
+
51
+ // Read event JSON from stdin
52
+ let input = '';
53
+ for await (const chunk of process.stdin) input += chunk;
54
+
55
+ let event;
56
+ try {
57
+ event = JSON.parse(input);
58
+ } catch {
59
+ audit('skip-parse-error', 'stdin not JSON');
60
+ process.exit(0);
61
+ }
62
+
63
+ const userPrompt = (event.prompt ?? event.user_message ?? event.message ?? '').toString();
64
+ const sessionId = event.session_id ?? event.sessionId ?? 'claude-code-unknown';
65
+ const transcriptPath = event.transcript_path ?? event.transcriptPath;
66
+
67
+ // Trivial prompts skip — too short to carry doctrine teaching.
68
+ if (!userPrompt || userPrompt.length < 20) {
69
+ audit('skip-trivial', `chars=${userPrompt.length}`);
70
+ process.exit(0);
71
+ }
72
+
73
+ // Skip slash commands — they're CLI-internal, not doctrine corrections.
74
+ if (/^\s*\//.test(userPrompt) && userPrompt.length < 200) {
75
+ audit('skip-slash-command', userPrompt.slice(0, 60));
76
+ process.exit(0);
77
+ }
78
+
79
+ // Correction-pattern detector. Each pattern below extracts a CANDIDATE
80
+ // trigger phrase (the action/pattern the user is correcting) along with
81
+ // the framing word ("don't", "stop", etc.). Patterns are intentionally
82
+ // high-recall + low-precision — the queue is for human review, not
83
+ // auto-promotion.
84
+ //
85
+ // Pattern groups:
86
+ // 1. Direct prohibitions: "don't ___", "do not ___", "never ___", "stop ___"
87
+ // 2. Doctrine assertions: "we said ___", "we don't ___", "we never ___"
88
+ // 3. Pattern-naming: "this is ___", "that's ___", "you're ___ing"
89
+ // 4. Doctrine vocabulary: anything containing "doctrine", "graceful", "fallback",
90
+ // "convenience", "shortcut", "lazy", "hack" with a 100-char neighborhood
91
+ // 5. Frustration markers: ALL-CAPS phrases ≥3 words (anger = high-priority signal)
92
+ const CORRECTION_PATTERNS = [
93
+ {
94
+ name: 'direct-prohibition',
95
+ rx: /\b(don'?t|do not|never|stop|quit|cease|enough)\s+(\w[\w\s]{4,80}?)(?=[.,!?\n]|$)/gi,
96
+ extractGroup: 2,
97
+ },
98
+ {
99
+ name: 'doctrine-assertion',
100
+ rx: /\b(we (?:said|don'?t|never|always|need to|don'?t use|never use)|i (?:said|told you|asked))\s+(\w[\w\s]{4,80}?)(?=[.,!?\n]|$)/gi,
101
+ extractGroup: 2,
102
+ },
103
+ {
104
+ name: 'pattern-naming',
105
+ rx: /\b(this is|that'?s|you'?re|you are|you keep|you'?re always)\s+(\w[\w\s]{4,80}?)(?=[.,!?\n]|$)/gi,
106
+ extractGroup: 2,
107
+ },
108
+ {
109
+ name: 'doctrine-vocab',
110
+ rx: /\b(doctrine|graceful|fallback|convenience|shortcut|lazy|hack|cheat|circumvent|bypass|skip|ignore|forget)[^.!?\n]{0,100}/gi,
111
+ extractGroup: 0,
112
+ },
113
+ {
114
+ name: 'frustration-allcaps',
115
+ rx: /\b([A-Z]{3,}\s+[A-Z]{3,}(?:\s+[A-Z]{3,})*)\b/g,
116
+ extractGroup: 1,
117
+ },
118
+ ];
119
+
120
+ const candidates = [];
121
+ for (const { name, rx, extractGroup } of CORRECTION_PATTERNS) {
122
+ for (const match of userPrompt.matchAll(rx)) {
123
+ const phrase = (match[extractGroup] || '').trim();
124
+ if (phrase.length < 8 || phrase.length > 200) continue;
125
+ candidates.push({
126
+ patternType: name,
127
+ phrase,
128
+ surroundingContext: match[0].slice(0, 240),
129
+ sourceIndex: match.index ?? 0,
130
+ });
131
+ }
132
+ }
133
+
134
+ // Deduplicate candidates that share the same first 30 chars (pattern variants
135
+ // of the same correction).
136
+ const seen = new Set();
137
+ const unique = [];
138
+ for (const c of candidates) {
139
+ const key = c.phrase.toLowerCase().slice(0, 30);
140
+ if (seen.has(key)) continue;
141
+ seen.add(key);
142
+ unique.push(c);
143
+ }
144
+
145
+ if (unique.length === 0) {
146
+ audit('skip-no-candidates', `chars=${userPrompt.length}`);
147
+ process.exit(0);
148
+ }
149
+
150
+ // Pull the most recent assistant text (last 2KB) so the queue entry shows
151
+ // what behavior the user is correcting. Without this, "don't do X" entries
152
+ // have no anchor to which assistant action triggered them.
153
+ let recentAssistantContext = '';
154
+ if (transcriptPath && existsSync(transcriptPath)) {
155
+ try {
156
+ const lines = readFileSync(transcriptPath, 'utf8').split('\n').filter(Boolean);
157
+ for (let i = lines.length - 1; i >= 0; i--) {
158
+ try {
159
+ const m = JSON.parse(lines[i]);
160
+ const role = m.message?.role ?? m.role;
161
+ if (role !== 'assistant') continue;
162
+ const content = m.message?.content ?? m.content ?? [];
163
+ if (!Array.isArray(content)) continue;
164
+ const text = content
165
+ .filter((b) => b?.type === 'text')
166
+ .map((b) => b.text || '')
167
+ .join('\n');
168
+ if (text) {
169
+ recentAssistantContext = text.slice(-2000);
170
+ break;
171
+ }
172
+ } catch {}
173
+ }
174
+ } catch {}
175
+ }
176
+
177
+ // Append candidates to the queue. Each entry is human-reviewable:
178
+ // - patternType: which detector caught it
179
+ // - candidatePhrase: the extracted phrase
180
+ // - userPromptExcerpt: 240 chars of context around the phrase
181
+ // - recentAssistantContext: what Claude did right before this correction
182
+ // - sessionId, ts: for traceability
183
+ try {
184
+ if (!existsSync(dirname(QUEUE))) mkdirSync(dirname(QUEUE), { recursive: true });
185
+ for (const c of unique) {
186
+ const entry = {
187
+ ts: new Date().toISOString(),
188
+ sessionId,
189
+ patternType: c.patternType,
190
+ candidatePhrase: c.phrase,
191
+ userPromptExcerpt: c.surroundingContext,
192
+ recentAssistantContext: recentAssistantContext.slice(0, 1500),
193
+ reviewStatus: 'pending',
194
+ proposedTriggerRegex: phraseToCandidateRegex(c.phrase),
195
+ proposedMemoryFile: null,
196
+ proposedTeaching: null,
197
+ };
198
+ appendFileSync(QUEUE, JSON.stringify(entry) + '\n');
199
+ }
200
+ audit('queued', `count=${unique.length} types=${[...new Set(unique.map((c) => c.patternType))].join(',')}`);
201
+ } catch (err) {
202
+ audit('skip-write-error', (err && err.message ? err.message : String(err)).slice(0, 200));
203
+ }
204
+
205
+ // Hook is non-blocking — UserPromptSubmit accepts no decision blocker for
206
+ // pure side-effect hooks. Exit clean so the harness packet + preprompt
207
+ // consult chain continues uninterrupted.
208
+ process.exit(0);
209
+
210
+ // ── Helpers ──────────────────────────────────────────────────────────────
211
+
212
+ // Convert a candidate phrase into a regex skeleton for the trigger map.
213
+ // Replaces tokens with character classes that allow minor variation (verb
214
+ // endings, plurals, optional punctuation). Output is a SUGGESTION; human
215
+ // reviewer refines before adding to doctrine_trigger_map.json.
216
+ function phraseToCandidateRegex(phrase) {
217
+ // Lowercase + escape regex specials.
218
+ const lower = phrase.toLowerCase();
219
+ const escaped = lower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
220
+ // Allow optional trailing 's' or 'ing' on the last word for verb forms.
221
+ const tokens = escaped.split(/\s+/);
222
+ if (tokens.length === 0) return escaped;
223
+ const last = tokens[tokens.length - 1];
224
+ // Don't bloat single-letter tokens.
225
+ if (last.length >= 4 && !last.endsWith('s') && !last.endsWith('ing')) {
226
+ tokens[tokens.length - 1] = `${last}(?:s|ing|ed)?`;
227
+ }
228
+ return tokens.join('\\s+');
229
+ }
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ // aria-userprompt-abandon-detect.mjs — UserPromptSubmit hook that detects
3
+ // mid-turn plan abandonment.
4
+ //
5
+ // When Aria's preprompt-consult issued a binding plan
6
+ // (~/.claude/aria-active-plan-${sessionId}.json) and the user's NEXT prompt
7
+ // changes direction without overlapping any active phase's keywords, that's
8
+ // a CONSCIOUS OVERRIDE — not silent context loss. The hook surfaces it to
9
+ // the session_audit table (gate_name='plan-abandonment', decision='warn')
10
+ // so orchestrators can detect mid-execution direction changes.
11
+ //
12
+ // Per Aria's consult 2026-04-27: "if the orchestrator sends a plan, then
13
+ // gets a user message that changes direction mid-execution, the binding
14
+ // needs to surface that as a *conscious override*, not a silent context
15
+ // loss." That gap was the third completion criterion for Aria-as-commander
16
+ // binding (#50).
17
+ //
18
+ // Detection algorithm:
19
+ // 1. Load active plan (skip if none — no plan = no abandonment possible)
20
+ // 2. Extract keywords from each active phase's summary + successCriterion
21
+ // 3. Tokenize incoming user prompt, drop stop-words
22
+ // 4. Compute keyword overlap between prompt tokens and phase keywords
23
+ // 5. If overlap < THRESHOLD (default: 1 keyword match across all phases),
24
+ // mark abandonment: write session_audit row + emit a brief warning
25
+ // injected into the next turn's context
26
+ //
27
+ // Non-blocking — UserPromptSubmit hooks don't block the user's prompt.
28
+ // Audit-only surface; the orchestrator decides what to do with the warning.
29
+ //
30
+ // No env-var kill-switch (Hamza 2026-04-27 — env-var disable paths gave
31
+ // the gated process free escape; doctrine violation). Disable = remove
32
+ // hook entry from ~/.claude/settings.json.
33
+
34
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
35
+ import { dirname } from 'node:path';
36
+
37
+ const HOME = process.env.HOME || '/tmp';
38
+ const LOG = `${HOME}/.claude/aria-abandon-detect.log`;
39
+
40
+ function audit(decision, summary) {
41
+ try {
42
+ if (!existsSync(dirname(LOG))) mkdirSync(dirname(LOG), { recursive: true });
43
+ appendFileSync(LOG, `${new Date().toISOString()} ${decision} ${summary}\n`);
44
+ } catch {}
45
+ }
46
+
47
+ // Env-var kill-switch removed 2026-04-27 per Hamza directive.
48
+
49
+ let input = '';
50
+ for await (const chunk of process.stdin) input += chunk;
51
+
52
+ let event;
53
+ try {
54
+ event = JSON.parse(input);
55
+ } catch {
56
+ audit('skip-parse-error', 'stdin not JSON');
57
+ process.exit(0);
58
+ }
59
+
60
+ const userPrompt = (event.prompt ?? event.user_message ?? event.message ?? '').toString();
61
+ const sessionId = (event.session_id ?? event.sessionId ?? 'claude-code-unknown').replace(/[^a-zA-Z0-9_-]/g, '_');
62
+
63
+ // Trivial prompts skip — too short to evaluate phase overlap meaningfully.
64
+ if (!userPrompt || userPrompt.length < 30) {
65
+ audit('skip-trivial', `chars=${userPrompt.length}`);
66
+ process.exit(0);
67
+ }
68
+
69
+ // Slash commands are CLI-internal, not direction changes.
70
+ if (/^\s*\//.test(userPrompt) && userPrompt.length < 200) {
71
+ audit('skip-slash-command', userPrompt.slice(0, 60));
72
+ process.exit(0);
73
+ }
74
+
75
+ const ACTIVE_PLAN_PATH = `${HOME}/.claude/aria-active-plan-${sessionId}.json`;
76
+
77
+ if (!existsSync(ACTIVE_PLAN_PATH)) {
78
+ audit('skip-no-active-plan', `session=${sessionId}`);
79
+ process.exit(0);
80
+ }
81
+
82
+ let plan;
83
+ try {
84
+ plan = JSON.parse(readFileSync(ACTIVE_PLAN_PATH, 'utf8'));
85
+ } catch (err) {
86
+ audit('skip-plan-parse-error', (err?.message || String(err)).slice(0, 120));
87
+ process.exit(0);
88
+ }
89
+
90
+ if (!plan || !Array.isArray(plan.phases) || plan.phases.length === 0) {
91
+ audit('skip-empty-plan', `planId=${plan?.planId || 'unknown'}`);
92
+ process.exit(0);
93
+ }
94
+
95
+ // Stop-words filter — same set as the auto-learn hook for consistency.
96
+ const STOPWORDS = new Set([
97
+ 'the','a','an','of','to','in','at','by','for','on','with','i','is','was','are',
98
+ 'were','this','that','as','it','and','or','but','from','into','about','have',
99
+ 'has','had','do','does','did','will','would','could','should','can','may',
100
+ 'might','must','shall','be','been','being','here','there','what','which','who',
101
+ 'how','why','when','where','some','any','all','each','every','no','not',
102
+ ]);
103
+
104
+ function tokenize(text) {
105
+ return new Set(
106
+ text.toLowerCase()
107
+ .replace(/[^a-z0-9\s_-]/g, ' ')
108
+ .split(/\s+/)
109
+ .filter((w) => w.length >= 4 && !STOPWORDS.has(w))
110
+ );
111
+ }
112
+
113
+ const promptTokens = tokenize(userPrompt);
114
+
115
+ // Aggregate phase keywords across all phases.
116
+ const phaseKeywordsSet = new Set();
117
+ for (const phase of plan.phases) {
118
+ const summary = String(phase.summary || '');
119
+ const successCriterion = String(phase.successCriterion || '');
120
+ const id = String(phase.id || '');
121
+ for (const t of tokenize(`${summary} ${successCriterion} ${id}`)) {
122
+ phaseKeywordsSet.add(t);
123
+ }
124
+ }
125
+
126
+ const overlap = [...promptTokens].filter((t) => phaseKeywordsSet.has(t));
127
+ // Threshold default raised to 2 per Aria consult 2026-04-27 — single-keyword
128
+ // match fires too often on benign intent shifts (especially in exploratory
129
+ // sessions); 2 filters noise while still catching genuine abandonment before
130
+ // drift compounds.
131
+ const OVERLAP_THRESHOLD = Number(process.env.ARIA_ABANDON_THRESHOLD || '2');
132
+ const isAbandonment = overlap.length < OVERLAP_THRESHOLD;
133
+
134
+ if (!isAbandonment) {
135
+ audit('aligned', `planId=${plan.planId || 'unknown'} overlap=${overlap.length} prompt-tokens=${promptTokens.size} phase-tokens=${phaseKeywordsSet.size}`);
136
+ process.exit(0);
137
+ }
138
+
139
+ // Abandonment detected — write session_audit row + emit warning context.
140
+ const harnessUrl =
141
+ process.env.ARIA_HIVE_RUNTIME_URL ||
142
+ process.env.ARIA_HARNESS_BASE_URL ||
143
+ process.env.ARIA_HARNESS_URL ||
144
+ 'https://harness.ariasos.com';
145
+ const harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
146
+
147
+ if (harnessToken) {
148
+ // Fire-and-forget POST — don't block the user's turn waiting for audit
149
+ // write. The audit log entry below records whether the request was even
150
+ // attempted, so silence in this block means the audit is missing AND
151
+ // that fact is itself audited locally.
152
+ fetch(`${harnessUrl}/api/harness/audit/session`, {
153
+ method: 'POST',
154
+ headers: {
155
+ 'Content-Type': 'application/json',
156
+ Authorization: `Bearer ${harnessToken}`,
157
+ },
158
+ body: JSON.stringify({
159
+ session_id: sessionId,
160
+ surface: 'claude-code-userprompt-abandon-detect',
161
+ gate_name: 'plan-abandonment',
162
+ decision: 'warn',
163
+ reason: `Mid-turn plan abandonment detected. Active plan ${plan.planId || 'unknown'} (${plan.phases.length} phases) has ${phaseKeywordsSet.size} aggregate phase-keywords; incoming user prompt (${promptTokens.size} content-tokens) overlaps only ${overlap.length} (threshold=${OVERLAP_THRESHOLD}).`,
164
+ evidence_json: {
165
+ planId: plan.planId,
166
+ promptExcerpt: userPrompt.slice(0, 400),
167
+ overlapTokens: overlap,
168
+ promptTokenCount: promptTokens.size,
169
+ phaseKeywordCount: phaseKeywordsSet.size,
170
+ phaseSummaries: plan.phases.map((p) => ({ id: p.id, summary: String(p.summary || '').slice(0, 120) })),
171
+ threshold: OVERLAP_THRESHOLD,
172
+ },
173
+ }),
174
+ }).catch(() => {/* network-level failure surfaces in audit log below */});
175
+ }
176
+
177
+ audit('abandonment-detected', `planId=${plan.planId || 'unknown'} overlap=${overlap.length}/${OVERLAP_THRESHOLD} prompt-tokens=${promptTokens.size} phase-tokens=${phaseKeywordsSet.size}`);
178
+
179
+ // Inject a warning into the next turn's context so the orchestrator (Claude)
180
+ // sees the abandonment signal explicitly rather than treating the new prompt
181
+ // as plan-aligned. The orchestrator can decide to: (a) acknowledge the
182
+ // override + clear the active plan, or (b) ask the user whether to continue
183
+ // the prior plan after this excursion.
184
+ const context = `[ARIA_PLAN_ABANDONMENT_DETECTED — active plan ${plan.planId || 'unknown'} has ${plan.phases.length} phase(s); incoming user prompt overlaps only ${overlap.length} keyword(s) with active phases (threshold=${OVERLAP_THRESHOLD}). This is a CONSCIOUS USER OVERRIDE, not silent context loss. Orchestrator should: (1) acknowledge the direction change to the user, (2) decide whether to clear the active plan or keep it for resumption, (3) write the decision to session_audit. Active phases were: ${plan.phases.map((p) => `${p.id}: ${String(p.summary || '').slice(0, 80)}`).join(' | ')}]`;
185
+
186
+ console.log(JSON.stringify({
187
+ hookSpecificOutput: {
188
+ hookEventName: 'UserPromptSubmit',
189
+ additionalContext: context,
190
+ },
191
+ }));
192
+ process.exit(0);