@aria_asi/cli 0.2.26 → 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 (248) 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 +80 -24
  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 +283 -0
  170. package/dist/sdk/index.js +622 -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 +109 -40
  177. package/hooks/aria-cognition-substrate-binding.mjs +676 -0
  178. package/hooks/aria-harness-via-sdk.mjs +34 -21
  179. package/hooks/aria-import-resolution-gate.mjs +330 -0
  180. package/hooks/aria-outcome-record.mjs +5 -1
  181. package/hooks/aria-pre-emit-dryrun.mjs +294 -0
  182. package/hooks/aria-pre-tool-gate.mjs +828 -41
  183. package/hooks/aria-preprompt-consult.mjs +113 -13
  184. package/hooks/aria-preturn-memory-gate.mjs +298 -6
  185. package/hooks/aria-repo-doctrine-gate.mjs +397 -0
  186. package/hooks/aria-stop-gate.mjs +739 -76
  187. package/hooks/aria-userprompt-abandon-detect.mjs +5 -1
  188. package/hooks/doctrine_trigger_map.json +209 -15
  189. package/hooks/lib/canonical-lenses.mjs +64 -0
  190. package/hooks/lib/gate-audit.mjs +43 -0
  191. package/opencode-plugins/harness-context/index.js +1 -1
  192. package/opencode-plugins/harness-context/inject-context.mjs +82 -23
  193. package/opencode-plugins/harness-gate/index.js +248 -0
  194. package/opencode-plugins/harness-outcome/index.js +129 -0
  195. package/opencode-plugins/harness-stop/index.js +241 -0
  196. package/package.json +8 -2
  197. package/runtime-src/doctor.mjs +23 -0
  198. package/runtime-src/local-phase.mjs +632 -0
  199. package/runtime-src/mizan-scheduler.mjs +331 -0
  200. package/runtime-src/provider-proxy.mjs +594 -0
  201. package/runtime-src/service.mjs +2708 -0
  202. package/scripts/bundle-sdk.mjs +317 -0
  203. package/scripts/install-client.sh +176 -0
  204. package/scripts/publish-all.sh +344 -0
  205. package/scripts/publish-docker.sh +27 -0
  206. package/scripts/validate-hook-contracts.mjs +54 -0
  207. package/scripts/validate-skill-prompts.mjs +95 -0
  208. package/skills/aria-cognition/aria-essence/SKILL.md +63 -0
  209. package/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
  210. package/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
  211. package/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
  212. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
  213. package/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
  214. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
  215. package/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
  216. package/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
  217. package/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
  218. package/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
  219. package/skills/aria-cognition/mizan/SKILL.md +72 -0
  220. package/skills/aria-cognition/nadia/SKILL.md +38 -0
  221. package/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
  222. package/skills/aria-cognition/predictor/SKILL.md +25 -0
  223. package/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
  224. package/skills/aria-cognition/soul-domains/SKILL.md +25 -0
  225. package/src/auth-commands.ts +111 -45
  226. package/src/chat.ts +174 -13
  227. package/src/codebase-scanner.ts +4 -0
  228. package/src/config.ts +15 -0
  229. package/src/connectors/claude-code.ts +79 -25
  230. package/src/connectors/codebase-awareness.ts +408 -0
  231. package/src/connectors/codex.ts +274 -0
  232. package/src/connectors/cognitive-skills.ts +51 -0
  233. package/src/connectors/opencode.ts +93 -4
  234. package/src/connectors/repo-git-hooks.ts +86 -0
  235. package/src/connectors/repo-guard.ts +589 -0
  236. package/src/connectors/runtime.ts +374 -0
  237. package/src/connectors/shell.ts +83 -14
  238. package/src/connectors/syncd.ts +488 -0
  239. package/src/decisions.ts +469 -0
  240. package/src/garden-control-plane.ts +101 -26
  241. package/src/github-connect.ts +143 -0
  242. package/src/harness-client.ts +128 -3
  243. package/src/hive-client.ts +165 -5
  244. package/src/index.ts +41 -2
  245. package/src/lib/aristotle-noor-wire.ts +310 -0
  246. package/src/providers/types.ts +6 -0
  247. package/src/runtime-proof.ts +392 -0
  248. package/src/setup-wizard.ts +37 -2
@@ -40,6 +40,19 @@ const HOOK_FILES = [
40
40
  // outcome-record fires on every state-mutating tool PostToolUse.
41
41
  'aria-discovery-record.mjs',
42
42
  'aria-outcome-record.mjs',
43
+ // Substrate-binding gate — validates cognition lenses cite real loaded
44
+ // substrate (axioms, frames, memories) not forged/generic anchors.
45
+ // Writes ~/.claude/.aria-loaded-substrate.json manifest (#142).
46
+ 'aria-cognition-substrate-binding.mjs',
47
+ // Repo-side doctrine gate — scans edited/staged source files for
48
+ // mock/stub/pending semantics in doctrine-bound packages.
49
+ 'aria-repo-doctrine-gate.mjs',
50
+ // Pre-emit dry-run validator (#140) — model self-checks draft against
51
+ // substrate-binding + drift-triggers before emit. CLI tool, not auto-hook.
52
+ 'aria-pre-emit-dryrun.mjs',
53
+ ];
54
+ const HOOK_DIRS = [
55
+ 'lib',
43
56
  ];
44
57
  // Compiled location: <pkg>/dist/aria-connector/src/connectors/claude-code.js
45
58
  // (tsc preserves the src/ rooted layout under outDir). From this file:
@@ -57,7 +70,12 @@ const HOOK_FILES = [
57
70
  // the fix lands in the same turn as the discovery.
58
71
  function packageHooksDir(): string {
59
72
  const here = path.dirname(fileURLToPath(import.meta.url));
60
- return path.resolve(here, '..', '..', '..', '..', 'hooks');
73
+ const candidates = [
74
+ path.resolve(here, '..', '..', '..', '..', 'hooks'),
75
+ path.resolve(here, '..', '..', '..', 'assets', 'hooks'),
76
+ ];
77
+ const found = candidates.find((candidate) => existsSync(candidate));
78
+ return found || candidates[0];
61
79
  }
62
80
 
63
81
  function packageSdkDir(): string {
@@ -177,15 +195,22 @@ const HOOKS_BLOCK = {
177
195
  }],
178
196
  },
179
197
  {
180
- // Outcome-record: fires on every state-mutating tool to accumulate the
181
- // outcome ledger (Sonnet H's #76 endpoint). Fire-and-forget — HTTP
182
- // failures in the hook do not block the tool pipeline.
183
198
  matcher: 'Bash|Edit|Write|NotebookEdit',
184
- hooks: [{
185
- type: 'command',
186
- command: 'node $HOME/.claude/hooks/aria-outcome-record.mjs',
187
- timeout: 3,
188
- }],
199
+ hooks: [
200
+ {
201
+ type: 'command',
202
+ command: 'node $HOME/.claude/hooks/aria-repo-doctrine-gate.mjs',
203
+ timeout: 5,
204
+ },
205
+ {
206
+ // Outcome-record: fires on every state-mutating tool to accumulate the
207
+ // outcome ledger (Sonnet H's #76 endpoint). Fire-and-forget — HTTP
208
+ // failures in the hook do not block the tool pipeline.
209
+ type: 'command',
210
+ command: 'node $HOME/.claude/hooks/aria-outcome-record.mjs',
211
+ timeout: 3,
212
+ },
213
+ ],
189
214
  },
190
215
  ],
191
216
  Stop: [{
@@ -197,6 +222,11 @@ const HOOKS_BLOCK = {
197
222
  // against probe evidence in this turn's tool results. Closes the
198
223
  // "WHEN TO LOOK before speaking" gap left open by both existing gates.
199
224
  hooks: [
225
+ {
226
+ type: 'command',
227
+ command: 'node $HOME/.claude/hooks/aria-cognition-substrate-binding.mjs',
228
+ timeout: 3,
229
+ },
200
230
  {
201
231
  type: 'command',
202
232
  command: 'node $HOME/.claude/hooks/aria-stop-gate.mjs',
@@ -217,6 +247,25 @@ function installHooks(claudeDir: string, logs: string[], opts: { force?: boolean
217
247
  mkdirSync(hooksTargetDir, { recursive: true, mode: 0o700 });
218
248
  }
219
249
  const sourceDir = packageHooksDir();
250
+ for (const dirName of HOOK_DIRS) {
251
+ const srcDir = path.join(sourceDir, dirName);
252
+ if (!existsSync(srcDir)) continue;
253
+ const dstDir = path.join(hooksTargetDir, dirName);
254
+ if (!existsSync(dstDir)) {
255
+ mkdirSync(dstDir, { recursive: true, mode: 0o700 });
256
+ }
257
+ for (const child of readdirSync(srcDir)) {
258
+ const src = path.join(srcDir, child);
259
+ const stat = statSync(src);
260
+ if (!stat.isFile()) continue;
261
+ const dst = path.join(dstDir, child);
262
+ copyFileSync(src, dst);
263
+ if (child.endsWith('.mjs') || child.endsWith('.js')) {
264
+ try { chmodSync(dst, 0o755); } catch {}
265
+ }
266
+ logs.push(`Installed hook helper: ${dirName}/${child}`);
267
+ }
268
+ }
220
269
  for (const name of HOOK_FILES) {
221
270
  const src = path.join(sourceDir, name);
222
271
  if (!existsSync(src)) {
@@ -294,29 +343,34 @@ function wireHooksBlock(settings: Record<string, unknown>, logs: string[]): void
294
343
  }
295
344
  const hooks = settings.hooks as Record<string, unknown[]>;
296
345
  let mergedEvents = 0;
346
+
347
+ const isManagedAriaCommand = (command: string): boolean =>
348
+ command.includes('$HOME/.claude/hooks/aria-');
349
+
350
+ const isSameManagedEntry = (entry: unknown, matcher: string, commands: string[]): boolean => {
351
+ if (typeof entry !== 'object' || entry === null) return false;
352
+ const eMatcher = (entry as { matcher?: string }).matcher ?? '';
353
+ if (eMatcher !== matcher) return false;
354
+ const eCommands = ((entry as { hooks?: Array<{ command?: string }> }).hooks ?? [])
355
+ .map((h) => h.command)
356
+ .filter((c): c is string => typeof c === 'string');
357
+ if (eCommands.length === 0) return false;
358
+ return eCommands.every(isManagedAriaCommand) || commands.every((c) => eCommands.includes(c));
359
+ };
360
+
297
361
  for (const [event, ourEntries] of Object.entries(HOOKS_BLOCK)) {
298
362
  const existingEntries = Array.isArray(hooks[event]) ? hooks[event] : [];
363
+ const normalizedEntries = [...existingEntries];
299
364
  for (const ourEntry of ourEntries) {
300
365
  const ourMatcher = (ourEntry as { matcher?: string }).matcher ?? '';
301
366
  const ourCommands = ((ourEntry as { hooks?: Array<{ command?: string }> }).hooks ?? [])
302
367
  .map((h) => h.command)
303
368
  .filter((c): c is string => typeof c === 'string');
304
- // Idempotent: skip if an entry with the same matcher AND same set
305
- // of commands already exists. Re-running connect stays clean.
306
- const alreadyPresent = existingEntries.some((e) => {
307
- if (typeof e !== 'object' || e === null) return false;
308
- const eMatcher = (e as { matcher?: string }).matcher ?? '';
309
- if (eMatcher !== ourMatcher) return false;
310
- const eCommands = ((e as { hooks?: Array<{ command?: string }> }).hooks ?? [])
311
- .map((h) => h.command)
312
- .filter((c): c is string => typeof c === 'string');
313
- return ourCommands.every((c) => eCommands.includes(c));
314
- });
315
- if (!alreadyPresent) {
316
- existingEntries.push(ourEntry);
317
- }
369
+ const filteredEntries = normalizedEntries.filter((e) => !isSameManagedEntry(e, ourMatcher, ourCommands));
370
+ normalizedEntries.length = 0;
371
+ normalizedEntries.push(...filteredEntries, ourEntry);
318
372
  }
319
- hooks[event] = existingEntries;
373
+ hooks[event] = normalizedEntries;
320
374
  mergedEvents++;
321
375
  }
322
376
  settings.hooks = hooks;
@@ -324,7 +378,7 @@ function wireHooksBlock(settings: Record<string, unknown>, logs: string[]): void
324
378
  if (!settings.$schema) {
325
379
  settings.$schema = 'https://json.schemastore.org/claude-code-settings.json';
326
380
  }
327
- logs.push(`Wired hooks into settings.json (${mergedEvents} events: SessionStart, UserPromptSubmit, PreToolUse [cognition+memory-gate+Agent-handoff], PostToolUse [Agent-ledger-merge + outcome-record], Stop) — merge-safe, preserves third-party entries`);
381
+ logs.push(`Wired hooks into settings.json (${mergedEvents} events: SessionStart, UserPromptSubmit, PreToolUse [cognition+memory-gate+Agent-handoff], PostToolUse [repo-doctrine-gate + Agent-ledger-merge + outcome-record], Stop) — merge-safe, preserves third-party entries`);
328
382
  }
329
383
 
330
384
  // Layer 2 (#84): hard prefix injected into every Agent dispatch so sub-agents
@@ -0,0 +1,408 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { createHash } from 'crypto';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
4
+ import { homedir } from 'os';
5
+ import * as path from 'path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import type { AriaConfig, LinkedRepo } from '../config.js';
8
+ import { loadConfig, saveConfig } from '../config.js';
9
+ import { scanCodebase, schemaImageToText, watchCodebase } from '../codebase-scanner.js';
10
+
11
+ const ARIA_DIR = path.join(homedir(), '.aria');
12
+ const BIN_DIR = path.join(ARIA_DIR, 'bin');
13
+ const ENV_PATH = path.join(ARIA_DIR, 'codebase-awareness.env');
14
+ const STATE_PATH = path.join(ARIA_DIR, 'codebase-awareness-state.json');
15
+ const INDEX_PATH = path.join(ARIA_DIR, 'codebase-awareness-index.json');
16
+
17
+ type AwarenessStatus = 'idle' | 'watching' | 'scanning' | 'error';
18
+
19
+ interface RepoSnapshot {
20
+ path: string;
21
+ name: string;
22
+ scanHash: string;
23
+ lastScan: string;
24
+ fileCount: number;
25
+ language: string;
26
+ framework: string | null;
27
+ packageManager: string | null;
28
+ database: string | null;
29
+ orm: string | null;
30
+ architecture: string | null;
31
+ }
32
+
33
+ interface AwarenessState {
34
+ status: AwarenessStatus;
35
+ watchedRepos: string[];
36
+ repoSnapshots: RepoSnapshot[];
37
+ lastError: string | null;
38
+ updatedAt: string;
39
+ }
40
+
41
+ function connectorPackageRoot(): string {
42
+ const here = path.dirname(fileURLToPath(import.meta.url));
43
+ const candidates = [
44
+ path.resolve(here, '..', '..'),
45
+ path.resolve(here, '..', '..', '..', '..'),
46
+ ];
47
+
48
+ for (const candidate of candidates) {
49
+ if (existsSync(path.join(candidate, 'bin', 'aria.js')) && existsSync(path.join(candidate, 'package.json'))) {
50
+ return candidate;
51
+ }
52
+ }
53
+
54
+ return candidates[candidates.length - 1];
55
+ }
56
+
57
+ function writeWrapper(binPath: string, args: string[]): void {
58
+ const ariaBin = path.join(connectorPackageRoot(), 'bin', 'aria.js');
59
+ writeFileSync(
60
+ binPath,
61
+ `#!/usr/bin/env bash\nexec node "${ariaBin}" ${args.join(' ')} "$@"\n`,
62
+ { mode: 0o755 },
63
+ );
64
+ }
65
+
66
+ function defaultState(): AwarenessState {
67
+ return {
68
+ status: 'idle',
69
+ watchedRepos: [],
70
+ repoSnapshots: [],
71
+ lastError: null,
72
+ updatedAt: new Date(0).toISOString(),
73
+ };
74
+ }
75
+
76
+ function readState(): AwarenessState {
77
+ try {
78
+ const parsed = JSON.parse(readFileSync(STATE_PATH, 'utf8')) as Partial<AwarenessState>;
79
+ return {
80
+ status: parsed.status || 'idle',
81
+ watchedRepos: Array.isArray(parsed.watchedRepos) ? parsed.watchedRepos : [],
82
+ repoSnapshots: Array.isArray(parsed.repoSnapshots) ? parsed.repoSnapshots as RepoSnapshot[] : [],
83
+ lastError: parsed.lastError || null,
84
+ updatedAt: parsed.updatedAt || new Date(0).toISOString(),
85
+ };
86
+ } catch {
87
+ return defaultState();
88
+ }
89
+ }
90
+
91
+ function writeState(update: Partial<AwarenessState>): AwarenessState {
92
+ const current = readState();
93
+ const next: AwarenessState = {
94
+ ...current,
95
+ ...update,
96
+ updatedAt: new Date().toISOString(),
97
+ };
98
+ mkdirSync(ARIA_DIR, { recursive: true, mode: 0o700 });
99
+ writeFileSync(STATE_PATH, JSON.stringify(next, null, 2) + '\n', { mode: 0o600 });
100
+ return next;
101
+ }
102
+
103
+ function writeIndex(payload: Record<string, unknown>): void {
104
+ mkdirSync(ARIA_DIR, { recursive: true, mode: 0o700 });
105
+ writeFileSync(INDEX_PATH, JSON.stringify(payload, null, 2) + '\n', { mode: 0o600 });
106
+ }
107
+
108
+ function looksLikeRepoRoot(candidate: string): boolean {
109
+ return existsSync(path.join(candidate, '.git'));
110
+ }
111
+
112
+ function findRepoRoot(startDir: string): string | null {
113
+ let current = path.resolve(startDir);
114
+ while (true) {
115
+ if (looksLikeRepoRoot(current)) return current;
116
+ const parent = path.dirname(current);
117
+ if (parent === current) return null;
118
+ current = parent;
119
+ }
120
+ }
121
+
122
+ function resolveRepos(config?: AriaConfig, explicit?: string[], fallbackRepoPath?: string): string[] {
123
+ if (explicit?.length) return explicit.map((repo) => path.resolve(repo)).filter(looksLikeRepoRoot);
124
+
125
+ const envRepos = process.env.ARIA_CODEBASE_AWARENESS_REPOS?.split(path.delimiter)
126
+ .map((repo) => repo.trim())
127
+ .filter(Boolean) || [];
128
+ if (envRepos.length) return envRepos.map((repo) => path.resolve(repo)).filter(looksLikeRepoRoot);
129
+
130
+ const loaded = config || loadConfig();
131
+ const configured = (loaded.repositories || [])
132
+ .map((repo) => path.resolve(repo.path))
133
+ .filter(looksLikeRepoRoot);
134
+ if (configured.length) return configured;
135
+
136
+ if (fallbackRepoPath) {
137
+ const resolvedFallback = findRepoRoot(fallbackRepoPath);
138
+ if (resolvedFallback) return [resolvedFallback];
139
+ }
140
+
141
+ const cwd = findRepoRoot(process.cwd());
142
+ return cwd ? [cwd] : [];
143
+ }
144
+
145
+ function hashSchema(text: string): string {
146
+ return createHash('sha256').update(text).digest('hex').slice(0, 16);
147
+ }
148
+
149
+ function architectureSummary(image: {
150
+ architecture?: unknown;
151
+ }): string | null {
152
+ if (Array.isArray(image.architecture) && image.architecture.length > 0) {
153
+ const first = image.architecture[0];
154
+ if (typeof first === 'string' && first.trim()) return first.trim();
155
+ if (first && typeof first === 'object' && typeof first.pattern === 'string' && first.pattern.trim()) {
156
+ return first.pattern.trim();
157
+ }
158
+ }
159
+ if (typeof image.architecture === 'string' && image.architecture.trim()) return image.architecture.trim();
160
+ return null;
161
+ }
162
+
163
+ async function applyRepoScan(repoRoot: string): Promise<RepoSnapshot> {
164
+ const image = await scanCodebase(repoRoot);
165
+ const schemaText = schemaImageToText(image);
166
+ const scanHash = hashSchema(schemaText);
167
+ const lastScan = new Date().toISOString();
168
+
169
+ const config = loadConfig();
170
+ const nextRepo: LinkedRepo = {
171
+ path: repoRoot,
172
+ name: image.projectName,
173
+ scanHash,
174
+ lastScan,
175
+ };
176
+ const repositories = [
177
+ ...(config.repositories || []).filter((repo) => path.resolve(repo.path) !== repoRoot),
178
+ nextRepo,
179
+ ];
180
+
181
+ saveConfig({
182
+ ...config,
183
+ repositories,
184
+ schemaImages: {
185
+ ...(config.schemaImages || {}),
186
+ [image.projectName]: schemaText,
187
+ },
188
+ });
189
+
190
+ const snapshot: RepoSnapshot = {
191
+ path: repoRoot,
192
+ name: image.projectName,
193
+ scanHash,
194
+ lastScan,
195
+ fileCount: image.fileCount,
196
+ language: image.language,
197
+ framework: image.framework || null,
198
+ packageManager: image.packageManager || null,
199
+ database: image.database || null,
200
+ orm: image.orm || null,
201
+ architecture: architectureSummary(image),
202
+ };
203
+
204
+ const current = readState();
205
+ const nextSnapshots = [
206
+ snapshot,
207
+ ...current.repoSnapshots.filter((entry) => path.resolve(entry.path) !== repoRoot),
208
+ ];
209
+ writeState({
210
+ status: 'watching',
211
+ watchedRepos: Array.from(new Set([repoRoot, ...current.watchedRepos])),
212
+ repoSnapshots: nextSnapshots,
213
+ lastError: null,
214
+ });
215
+ writeIndex({
216
+ generatedAt: new Date().toISOString(),
217
+ watchedRepos: Array.from(new Set([repoRoot, ...current.watchedRepos])),
218
+ repoSnapshots: nextSnapshots,
219
+ schemaImages: loadConfig().schemaImages || {},
220
+ });
221
+ return snapshot;
222
+ }
223
+
224
+ export function readCodebaseAwarenessState(): AwarenessState {
225
+ return readState();
226
+ }
227
+
228
+ export async function runCodebaseAwarenessScan(options: {
229
+ repos?: string[];
230
+ fallbackRepoPath?: string;
231
+ } = {}): Promise<{ ok: true; repos: RepoSnapshot[] }> {
232
+ const repos = resolveRepos(undefined, options.repos, options.fallbackRepoPath);
233
+ if (!repos.length) {
234
+ writeState({ status: 'error', watchedRepos: [], lastError: 'No git repositories configured for codebase awareness.' });
235
+ throw new Error('No git repositories configured for codebase awareness.');
236
+ }
237
+
238
+ writeState({ status: 'scanning', watchedRepos: repos, lastError: null });
239
+ const snapshots: RepoSnapshot[] = [];
240
+ for (const repo of repos) {
241
+ snapshots.push(await applyRepoScan(repo));
242
+ }
243
+ writeState({ status: 'watching', watchedRepos: repos, repoSnapshots: snapshots, lastError: null });
244
+ writeIndex({
245
+ generatedAt: new Date().toISOString(),
246
+ watchedRepos: repos,
247
+ repoSnapshots: snapshots,
248
+ schemaImages: loadConfig().schemaImages || {},
249
+ });
250
+ return { ok: true, repos: snapshots };
251
+ }
252
+
253
+ export async function startCodebaseAwarenessDaemon(options: {
254
+ repos?: string[];
255
+ fallbackRepoPath?: string;
256
+ } = {}): Promise<void> {
257
+ const repos = resolveRepos(undefined, options.repos, options.fallbackRepoPath);
258
+ if (!repos.length) {
259
+ writeState({ status: 'error', watchedRepos: [], lastError: 'No git repositories configured for codebase awareness.' });
260
+ throw new Error('No git repositories configured for codebase awareness.');
261
+ }
262
+
263
+ await runCodebaseAwarenessScan({ repos });
264
+ writeState({ status: 'watching', watchedRepos: repos, lastError: null });
265
+
266
+ for (const repo of repos) {
267
+ watchCodebase(
268
+ repo,
269
+ (image) => {
270
+ const schemaText = schemaImageToText(image);
271
+ const scanHash = hashSchema(schemaText);
272
+ const lastScan = new Date().toISOString();
273
+ const config = loadConfig();
274
+ const repositories = [
275
+ ...(config.repositories || []).filter((entry) => path.resolve(entry.path) !== repo),
276
+ {
277
+ path: repo,
278
+ name: image.projectName,
279
+ scanHash,
280
+ lastScan,
281
+ },
282
+ ];
283
+ const nextConfig = {
284
+ ...config,
285
+ repositories,
286
+ schemaImages: {
287
+ ...(config.schemaImages || {}),
288
+ [image.projectName]: schemaText,
289
+ },
290
+ };
291
+ saveConfig(nextConfig);
292
+
293
+ const current = readState();
294
+ const snapshot: RepoSnapshot = {
295
+ path: repo,
296
+ name: image.projectName,
297
+ scanHash,
298
+ lastScan,
299
+ fileCount: image.fileCount,
300
+ language: image.language,
301
+ framework: image.framework || null,
302
+ packageManager: image.packageManager || null,
303
+ database: image.database || null,
304
+ orm: image.orm || null,
305
+ architecture: architectureSummary(image),
306
+ };
307
+ const repoSnapshots = [
308
+ snapshot,
309
+ ...current.repoSnapshots.filter((entry) => path.resolve(entry.path) !== repo),
310
+ ];
311
+ writeState({
312
+ status: 'watching',
313
+ watchedRepos: repos,
314
+ repoSnapshots,
315
+ lastError: null,
316
+ });
317
+ writeIndex({
318
+ generatedAt: new Date().toISOString(),
319
+ watchedRepos: repos,
320
+ repoSnapshots,
321
+ schemaImages: nextConfig.schemaImages || {},
322
+ });
323
+ },
324
+ {
325
+ debounceMs: 800,
326
+ pollIntervalMs: 4000,
327
+ onError: ({ error }) => {
328
+ writeState({
329
+ status: 'error',
330
+ watchedRepos: repos,
331
+ lastError: error.message,
332
+ });
333
+ },
334
+ },
335
+ );
336
+ }
337
+
338
+ await new Promise(() => {});
339
+ }
340
+
341
+ function writeEnv(repos: string[]): string {
342
+ mkdirSync(ARIA_DIR, { recursive: true, mode: 0o700 });
343
+ writeFileSync(
344
+ ENV_PATH,
345
+ [
346
+ `ARIA_CODEBASE_AWARENESS_REPOS=${repos.join(path.delimiter)}`,
347
+ '',
348
+ ].join('\n'),
349
+ { mode: 0o600 },
350
+ );
351
+ return ENV_PATH;
352
+ }
353
+
354
+ function installSystemdUserService(logs: string[]): void {
355
+ const systemctlPath = '/bin/systemctl';
356
+ if (process.platform !== 'linux' || !existsSync(systemctlPath)) return;
357
+
358
+ const serviceDir = path.join(homedir(), '.config', 'systemd', 'user');
359
+ const servicePath = path.join(serviceDir, 'aria-codebase-awareness.service');
360
+ mkdirSync(serviceDir, { recursive: true, mode: 0o755 });
361
+
362
+ const unit = [
363
+ '[Unit]',
364
+ 'Description=Aria Codebase Awareness',
365
+ 'After=network.target',
366
+ '',
367
+ '[Service]',
368
+ 'Type=simple',
369
+ `EnvironmentFile=-${ENV_PATH}`,
370
+ `ExecStart=${path.join(BIN_DIR, 'aria-codebase-awareness')} start`,
371
+ 'Restart=always',
372
+ 'RestartSec=2',
373
+ '',
374
+ '[Install]',
375
+ 'WantedBy=default.target',
376
+ '',
377
+ ].join('\n');
378
+
379
+ writeFileSync(servicePath, unit, { mode: 0o644 });
380
+ logs.push(`Installed systemd user unit → ${servicePath}`);
381
+
382
+ try {
383
+ execFileSync(systemctlPath, ['--user', 'daemon-reload'], { stdio: 'ignore' });
384
+ execFileSync(systemctlPath, ['--user', 'enable', '--now', 'aria-codebase-awareness.service'], { stdio: 'ignore' });
385
+ logs.push('Enabled and started systemd user service: aria-codebase-awareness.service');
386
+ } catch (error) {
387
+ logs.push(`⚠ codebase awareness service installed but not activated: ${error instanceof Error ? error.message : String(error)}`);
388
+ }
389
+ }
390
+
391
+ export async function installCodebaseAwarenessDaemon(config?: AriaConfig, fallbackRepoPath?: string): Promise<string[]> {
392
+ const repos = resolveRepos(config, undefined, fallbackRepoPath);
393
+ const logs: string[] = [];
394
+ if (!repos.length) {
395
+ logs.push('Skipped codebase awareness install: no git repositories configured.');
396
+ return logs;
397
+ }
398
+
399
+ mkdirSync(BIN_DIR, { recursive: true, mode: 0o755 });
400
+ const envPath = writeEnv(repos);
401
+ writeWrapper(path.join(BIN_DIR, 'aria-codebase-awareness'), ['codebase-awareness']);
402
+ installSystemdUserService(logs);
403
+
404
+ logs.push(`Installed codebase awareness wrapper → ${path.join(BIN_DIR, 'aria-codebase-awareness')}`);
405
+ logs.push(`Installed codebase awareness env → ${envPath}`);
406
+ logs.push(`Codebase awareness watching ${repos.length} repos`);
407
+ return logs;
408
+ }