@aria_asi/cli 0.2.36 → 0.2.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/CLIENT-ONBOARDING.md +4 -2
  2. package/bin/aria.js +11 -7
  3. package/dist/aria-connector/src/auth.d.ts +14 -0
  4. package/dist/aria-connector/src/auth.d.ts.map +1 -1
  5. package/dist/aria-connector/src/auth.js +103 -1
  6. package/dist/aria-connector/src/auth.js.map +1 -1
  7. package/dist/aria-connector/src/chat.d.ts.map +1 -1
  8. package/dist/aria-connector/src/chat.js +13 -8
  9. package/dist/aria-connector/src/chat.js.map +1 -1
  10. package/dist/aria-connector/src/config.d.ts +6 -1
  11. package/dist/aria-connector/src/config.d.ts.map +1 -1
  12. package/dist/aria-connector/src/config.js.map +1 -1
  13. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  14. package/dist/aria-connector/src/connectors/claude-code.js +50 -6
  15. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  16. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  17. package/dist/aria-connector/src/connectors/codex.js +290 -32
  18. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  19. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  20. package/dist/aria-connector/src/connectors/opencode.js +35 -11
  21. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  22. package/dist/aria-connector/src/connectors/repo-guard.d.ts +10 -0
  23. package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -1
  24. package/dist/aria-connector/src/connectors/repo-guard.js +110 -164
  25. package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -1
  26. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
  27. package/dist/aria-connector/src/connectors/runtime.js +17 -7
  28. package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
  29. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  30. package/dist/aria-connector/src/connectors/shell.js +12 -8
  31. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  32. package/dist/aria-connector/src/harness-client.d.ts +3 -1
  33. package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
  34. package/dist/aria-connector/src/harness-client.js +7 -20
  35. package/dist/aria-connector/src/harness-client.js.map +1 -1
  36. package/dist/aria-connector/src/model-context.d.ts.map +1 -1
  37. package/dist/aria-connector/src/model-context.js +5 -0
  38. package/dist/aria-connector/src/model-context.js.map +1 -1
  39. package/dist/aria-connector/src/providers/types.d.ts +1 -1
  40. package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
  41. package/dist/aria-connector/src/providers/xai.d.ts +3 -0
  42. package/dist/aria-connector/src/providers/xai.d.ts.map +1 -0
  43. package/dist/aria-connector/src/providers/xai.js +40 -0
  44. package/dist/aria-connector/src/providers/xai.js.map +1 -0
  45. package/dist/aria-connector/src/setup-wizard.js +1 -0
  46. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  47. package/dist/aria-connector/src/types.d.ts +2 -0
  48. package/dist/aria-connector/src/types.d.ts.map +1 -1
  49. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +51 -9
  50. package/dist/assets/hooks/aria-first-class-coach.mjs +129 -0
  51. package/dist/assets/hooks/aria-harness-via-sdk.mjs +33 -6
  52. package/dist/assets/hooks/aria-pre-tool-gate.mjs +86 -8
  53. package/dist/assets/hooks/aria-pre-tool-use.mjs +75 -0
  54. package/dist/assets/hooks/aria-preprompt-consult.mjs +5 -6
  55. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +5 -0
  56. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +15 -0
  57. package/dist/assets/hooks/aria-stop-gate.mjs +125 -17
  58. package/dist/assets/hooks/doctrine_trigger_map.json +11 -0
  59. package/dist/assets/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  60. package/dist/assets/hooks/lib/emergency-gateoff.mjs +6 -0
  61. package/dist/assets/hooks/lib/first-class-coach.mjs +755 -0
  62. package/dist/assets/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  63. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -14
  64. package/dist/assets/opencode-plugins/harness-context/auth-token.mjs +126 -0
  65. package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +62 -22
  66. package/dist/assets/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  67. package/dist/assets/opencode-plugins/harness-gate/index.js +87 -27
  68. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  69. package/dist/assets/opencode-plugins/harness-outcome/index.js +29 -24
  70. package/dist/assets/opencode-plugins/harness-stop/index.js +229 -68
  71. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  72. package/dist/runtime/auth-token.mjs +121 -0
  73. package/dist/runtime/coach-kernel.mjs +377 -0
  74. package/dist/runtime/codex-bridge.mjs +440 -69
  75. package/dist/runtime/discipline/doctrine_trigger_map.json +11 -0
  76. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  77. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  78. package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  79. package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  80. package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  81. package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  82. package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  83. package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +18 -0
  84. package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +18 -0
  85. package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  86. package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +18 -0
  87. package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  88. package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  89. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +18 -0
  90. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +18 -0
  91. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +18 -0
  92. package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +18 -0
  93. package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +18 -0
  94. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +18 -0
  95. package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +18 -0
  96. package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +18 -0
  97. package/dist/runtime/doctrine_trigger_map.json +11 -0
  98. package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +51 -9
  99. package/dist/runtime/hooks/aria-first-class-coach.mjs +129 -0
  100. package/dist/runtime/hooks/aria-harness-via-sdk.mjs +33 -6
  101. package/dist/runtime/hooks/aria-pre-tool-gate.mjs +86 -8
  102. package/dist/runtime/hooks/aria-pre-tool-use.mjs +75 -0
  103. package/dist/runtime/hooks/aria-preprompt-consult.mjs +5 -6
  104. package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +5 -0
  105. package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +15 -0
  106. package/dist/runtime/hooks/aria-stop-gate.mjs +125 -17
  107. package/dist/runtime/hooks/doctrine_trigger_map.json +11 -0
  108. package/dist/runtime/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  109. package/dist/runtime/hooks/lib/emergency-gateoff.mjs +6 -0
  110. package/dist/runtime/hooks/lib/first-class-coach.mjs +755 -0
  111. package/dist/runtime/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  112. package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +1 -14
  113. package/dist/runtime/local-phase.mjs +8 -0
  114. package/dist/runtime/manifest.json +2 -2
  115. package/dist/runtime/provider-proxy.mjs +136 -33
  116. package/dist/runtime/sdk/BUNDLED.json +2 -2
  117. package/dist/runtime/sdk/auth.d.ts +17 -0
  118. package/dist/runtime/sdk/auth.js +158 -0
  119. package/dist/runtime/sdk/auth.js.map +1 -0
  120. package/dist/runtime/sdk/index.d.ts +8 -1
  121. package/dist/runtime/sdk/index.js +15 -1
  122. package/dist/runtime/sdk/index.js.map +1 -1
  123. package/dist/runtime/service.mjs +1711 -74
  124. package/dist/runtime/task-project-ledger.mjs +290 -0
  125. package/dist/sdk/BUNDLED.json +2 -2
  126. package/dist/sdk/auth.d.ts +17 -0
  127. package/dist/sdk/auth.js +158 -0
  128. package/dist/sdk/auth.js.map +1 -0
  129. package/dist/sdk/index.d.ts +8 -1
  130. package/dist/sdk/index.js +15 -1
  131. package/dist/sdk/index.js.map +1 -1
  132. package/hooks/aria-cognition-substrate-binding.mjs +51 -9
  133. package/hooks/aria-first-class-coach.mjs +129 -0
  134. package/hooks/aria-harness-via-sdk.mjs +33 -6
  135. package/hooks/aria-pre-tool-gate.mjs +86 -8
  136. package/hooks/aria-pre-tool-use.mjs +75 -0
  137. package/hooks/aria-preprompt-consult.mjs +5 -6
  138. package/hooks/aria-preturn-memory-gate.mjs +5 -0
  139. package/hooks/aria-repo-doctrine-gate.mjs +15 -0
  140. package/hooks/aria-stop-gate.mjs +125 -17
  141. package/hooks/doctrine_trigger_map.json +11 -0
  142. package/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  143. package/hooks/lib/emergency-gateoff.mjs +6 -0
  144. package/hooks/lib/first-class-coach.mjs +755 -0
  145. package/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  146. package/hooks/lib/skill-autoload-gate.mjs +1 -14
  147. package/opencode-plugins/harness-context/auth-token.mjs +126 -0
  148. package/opencode-plugins/harness-context/inject-context.mjs +62 -22
  149. package/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  150. package/opencode-plugins/harness-gate/index.js +87 -27
  151. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  152. package/opencode-plugins/harness-outcome/index.js +29 -24
  153. package/opencode-plugins/harness-stop/index.js +229 -68
  154. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  155. package/package.json +8 -2
  156. package/runtime-src/auth-token.mjs +121 -0
  157. package/runtime-src/coach-kernel.mjs +377 -0
  158. package/runtime-src/codex-bridge.mjs +440 -69
  159. package/runtime-src/local-phase.mjs +8 -0
  160. package/runtime-src/provider-proxy.mjs +136 -33
  161. package/runtime-src/service.mjs +1711 -74
  162. package/scripts/bundle-sdk.mjs +8 -0
  163. package/scripts/check-client-compatibility.mjs +422 -0
  164. package/scripts/check-coach-kernel.mjs +204 -0
  165. package/scripts/check-managed-runtime-ledger.mjs +107 -0
  166. package/scripts/check-opencode-config-contract.mjs +78 -0
  167. package/scripts/check-quality-ledger.mjs +121 -0
  168. package/scripts/self-test-harness-gates.mjs +179 -11
  169. package/scripts/self-test-repo-guard.mjs +38 -0
  170. package/scripts/validate-skill-prompts.mjs +14 -1
  171. package/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  172. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  173. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  174. package/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  175. package/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  176. package/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  177. package/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  178. package/skills/aria-cognition/mizan/SKILL.md +18 -0
  179. package/skills/aria-cognition/nadia/SKILL.md +18 -0
  180. package/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  181. package/skills/aria-cognition/predictor/SKILL.md +18 -0
  182. package/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  183. package/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  184. package/src/auth.ts +136 -1
  185. package/src/chat.ts +13 -8
  186. package/src/config.ts +6 -1
  187. package/src/connectors/claude-code.ts +62 -18
  188. package/src/connectors/codex.ts +288 -32
  189. package/src/connectors/opencode.ts +35 -12
  190. package/src/connectors/repo-guard.ts +117 -172
  191. package/src/connectors/runtime.ts +19 -7
  192. package/src/connectors/shell.ts +12 -8
  193. package/src/harness-client.ts +8 -22
  194. package/src/model-context.ts +6 -0
  195. package/src/providers/types.ts +1 -1
  196. package/src/providers/xai.ts +55 -0
  197. package/src/setup-wizard.ts +1 -0
  198. package/src/types.ts +2 -0
@@ -0,0 +1,290 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, appendFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { homedir } from 'node:os';
4
+ import { basename, dirname, join } from 'node:path';
5
+
6
+ export const TASK_PROJECT_LEDGER_VERSION = 'aria.task_project_ledger.v1';
7
+
8
+ const CLAIM_RX = /\b(?:first[- ]class|production[- ]ready|ready|complete|completed|done|verified|gold[- ]standard|shipped|fixed|landed)\b/i;
9
+ const MAX_LEDGER_EVENTS = 500;
10
+
11
+ export const TASK_PROJECT_QUALITY_GATES = [
12
+ { id: 'ledger_created', readinessRequired: true, description: 'A durable task/project ledger exists before work proceeds.' },
13
+ { id: 'intent_boundary', readinessRequired: true, description: 'The ledger records what task or project is being attempted.' },
14
+ { id: 'scope_boundary', readinessRequired: true, description: 'The ledger records platform and phase boundaries.' },
15
+ { id: 'execution_trace', readinessRequired: false, description: 'The ledger records execution, tool, workflow, or background events.' },
16
+ { id: 'verification_trace', readinessRequired: true, description: 'The ledger records real verification evidence before completion/readiness claims.' },
17
+ { id: 'post_turn_writeback', readinessRequired: false, description: 'The ledger records post-turn or outcome writeback.' },
18
+ { id: 'final_claim_guard', readinessRequired: true, description: 'The ledger guards final claims against missing evidence.' },
19
+ ];
20
+
21
+ function stableHash(value = '') {
22
+ return createHash('sha256').update(String(value || 'unknown')).digest('hex').slice(0, 20);
23
+ }
24
+
25
+ function firstString(...values) {
26
+ for (const value of values) {
27
+ if (typeof value === 'string' && value.trim()) return value.trim();
28
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
29
+ }
30
+ return '';
31
+ }
32
+
33
+ function compactHint(value = '') {
34
+ const raw = String(value || '').trim();
35
+ if (!raw) return 'unknown';
36
+ const tail = raw.includes('/') ? basename(raw) : raw;
37
+ return tail.replace(/[^a-zA-Z0-9._-]+/g, '-').slice(0, 80) || 'unknown';
38
+ }
39
+
40
+ function readJsonFile(path) {
41
+ try {
42
+ if (!existsSync(path)) return null;
43
+ return JSON.parse(readFileSync(path, 'utf8'));
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ function writeJsonAtomic(path, value) {
50
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
51
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
52
+ writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
53
+ renameSync(tmp, path);
54
+ }
55
+
56
+ function markGate(ledger, gateId, status, evidence = undefined) {
57
+ const gate = ledger.qualityGates?.[gateId];
58
+ if (!gate) return;
59
+ if (gate.status === 'passed' && status !== 'blocked') return;
60
+ gate.status = status;
61
+ gate.updatedAt = ledger.updatedAt;
62
+ if (evidence) gate.evidence = evidence;
63
+ }
64
+
65
+ function hasPromptIntent(event = {}) {
66
+ return Boolean(firstString(
67
+ event.prompt,
68
+ event.userPrompt,
69
+ event.user_input,
70
+ event.message,
71
+ event.text,
72
+ event.body,
73
+ event.sessionID,
74
+ event.sessionId,
75
+ event.context?.prompt,
76
+ event.context?.bodyForAgent
77
+ ));
78
+ }
79
+
80
+ function hasVerificationEvidence(evidence = {}, event = {}) {
81
+ if (evidence.warning_clean === true || evidence.verification === true || evidence.validation_run === true) return true;
82
+ if (event.verified === true || event.validation?.ok === true || event.test?.ok === true) return true;
83
+ const text = firstString(event.verification, event.validation, event.testResult, event.commandResult, evidence.commandResult, evidence.outputPreview);
84
+ return /\b(?:ok|passed|pass|success|succeeded|exit\s*0|0\s*failures?)\b/i.test(text);
85
+ }
86
+
87
+ function isExecutionPhase(phase = '') {
88
+ return ['pre_tool', 'post_tool', 'background_worker', 'task_record', 'task_flow', 'lobster_run', 'approval_resume'].includes(phase);
89
+ }
90
+
91
+ function isPostTurnPhase(phase = '') {
92
+ return ['post_turn', 'outcome_writeback', 'decision_log'].includes(phase);
93
+ }
94
+
95
+ export function taskProjectLedgerRoot(env = process.env) {
96
+ return env.ARIA_TASK_PROJECT_LEDGER_DIR || join(homedir(), '.aria', 'task-project-ledgers');
97
+ }
98
+
99
+ export function resolveTaskProjectIdentity(event = {}, env = process.env) {
100
+ const projectRaw = firstString(
101
+ env.ARIA_PROJECT_ID,
102
+ event.projectId,
103
+ event.project_id,
104
+ event.project,
105
+ event.workspaceDir,
106
+ event.workspace_dir,
107
+ event.cwd,
108
+ event.currentWorkingDirectory,
109
+ event.context?.workspaceDir,
110
+ env.PWD,
111
+ process.cwd()
112
+ );
113
+ const taskRaw = firstString(
114
+ env.ARIA_TASK_ID,
115
+ event.taskId,
116
+ event.task_id,
117
+ event.sessionID,
118
+ event.session_id,
119
+ event.sessionId,
120
+ event.sessionKey,
121
+ event.conversationId,
122
+ event.threadId,
123
+ event.messageId,
124
+ event.transcript_path,
125
+ event.prompt,
126
+ event.message,
127
+ projectRaw
128
+ );
129
+ const projectHash = stableHash(projectRaw || 'unknown-project');
130
+ const taskHash = stableHash(`${projectHash}:${taskRaw || 'unknown-task'}`);
131
+ return {
132
+ projectHash,
133
+ taskHash,
134
+ projectHint: compactHint(projectRaw),
135
+ taskHint: compactHint(taskRaw),
136
+ };
137
+ }
138
+
139
+ export function taskProjectLedgerPaths(identity, env = process.env) {
140
+ const dir = join(taskProjectLedgerRoot(env), identity.projectHash);
141
+ return {
142
+ dir,
143
+ ledgerPath: join(dir, `${identity.taskHash}.json`),
144
+ eventPath: join(dir, `${identity.taskHash}.events.jsonl`),
145
+ };
146
+ }
147
+
148
+ function createTaskProjectLedger(identity, now) {
149
+ const qualityGates = Object.fromEntries(TASK_PROJECT_QUALITY_GATES.map((gate) => [
150
+ gate.id,
151
+ {
152
+ status: gate.id === 'ledger_created' || gate.id === 'final_claim_guard' ? 'passed' : 'pending',
153
+ description: gate.description,
154
+ readinessRequired: gate.readinessRequired,
155
+ updatedAt: now,
156
+ },
157
+ ]));
158
+ return {
159
+ schema: `${TASK_PROJECT_LEDGER_VERSION}.ledger`,
160
+ version: TASK_PROJECT_LEDGER_VERSION,
161
+ ledgerId: `tpl_${identity.projectHash}_${identity.taskHash}`,
162
+ createdAt: now,
163
+ updatedAt: now,
164
+ status: 'active',
165
+ identity,
166
+ controls: {
167
+ readinessClaimAllowed: false,
168
+ readinessClaimRule: 'Completion/readiness claims require all readinessRequired quality gates to pass and no open blockers.',
169
+ },
170
+ qualityGates,
171
+ phaseEvents: [],
172
+ evidence: [],
173
+ openBlockers: [],
174
+ blockedClaims: [],
175
+ };
176
+ }
177
+
178
+ export function readinessMissingGates(ledger = {}) {
179
+ const qualityGates = ledger.qualityGates || {};
180
+ return TASK_PROJECT_QUALITY_GATES
181
+ .filter((gate) => gate.readinessRequired)
182
+ .filter((gate) => qualityGates[gate.id]?.status !== 'passed' && qualityGates[gate.id]?.status !== 'not_applicable')
183
+ .map((gate) => gate.id);
184
+ }
185
+
186
+ export function updateTaskProjectLedger({ platform, phase, source = 'task-project-ledger', event = {}, evidence = {}, env = process.env } = {}) {
187
+ const now = new Date().toISOString();
188
+ const identity = resolveTaskProjectIdentity(event, env);
189
+ const paths = taskProjectLedgerPaths(identity, env);
190
+ const existing = readJsonFile(paths.ledgerPath);
191
+ const ledger = existing?.schema === `${TASK_PROJECT_LEDGER_VERSION}.ledger`
192
+ ? existing
193
+ : createTaskProjectLedger(identity, now);
194
+
195
+ ledger.updatedAt = now;
196
+ ledger.qualityGates = ledger.qualityGates || {};
197
+ for (const gate of TASK_PROJECT_QUALITY_GATES) {
198
+ if (!ledger.qualityGates[gate.id]) {
199
+ ledger.qualityGates[gate.id] = {
200
+ status: 'pending',
201
+ description: gate.description,
202
+ readinessRequired: gate.readinessRequired,
203
+ updatedAt: now,
204
+ };
205
+ }
206
+ }
207
+
208
+ markGate(ledger, 'ledger_created', 'passed', { ledgerPath: paths.ledgerPath });
209
+ if (hasPromptIntent(event) || phase === 'pre_prompt_injection' || phase === 'session_start') markGate(ledger, 'intent_boundary', 'passed', { source, phase });
210
+ if (platform && phase && phase !== 'unknown') markGate(ledger, 'scope_boundary', 'passed', { platform, phase });
211
+ if (isExecutionPhase(phase)) markGate(ledger, 'execution_trace', 'passed', { platform, phase, source });
212
+ if (hasVerificationEvidence(evidence, event)) markGate(ledger, 'verification_trace', 'passed', { source, phase });
213
+ if (isPostTurnPhase(phase)) markGate(ledger, 'post_turn_writeback', 'passed', { platform, phase, source });
214
+ markGate(ledger, 'final_claim_guard', 'passed', { source: 'task_project_ledger_claim_guard' });
215
+
216
+ const row = {
217
+ at: now,
218
+ platform: platform || 'unknown',
219
+ phase: phase || 'unknown',
220
+ source,
221
+ evidence,
222
+ };
223
+ ledger.phaseEvents = Array.isArray(ledger.phaseEvents) ? ledger.phaseEvents : [];
224
+ ledger.phaseEvents.push(row);
225
+ if (ledger.phaseEvents.length > MAX_LEDGER_EVENTS) ledger.phaseEvents = ledger.phaseEvents.slice(-MAX_LEDGER_EVENTS);
226
+ if (Object.keys(evidence || {}).length > 0) {
227
+ ledger.evidence = Array.isArray(ledger.evidence) ? ledger.evidence : [];
228
+ ledger.evidence.push({ at: now, source, phase: row.phase, evidence });
229
+ if (ledger.evidence.length > MAX_LEDGER_EVENTS) ledger.evidence = ledger.evidence.slice(-MAX_LEDGER_EVENTS);
230
+ }
231
+
232
+ const missingReadinessGates = readinessMissingGates(ledger);
233
+ ledger.controls = ledger.controls || {};
234
+ ledger.controls.readinessClaimAllowed = missingReadinessGates.length === 0 && (ledger.openBlockers || []).length === 0;
235
+ ledger.controls.missingReadinessGates = missingReadinessGates;
236
+
237
+ writeJsonAtomic(paths.ledgerPath, ledger);
238
+ mkdirSync(dirname(paths.eventPath), { recursive: true, mode: 0o700 });
239
+ appendFileSync(paths.eventPath, `${JSON.stringify({ schema: `${TASK_PROJECT_LEDGER_VERSION}.event`, ledgerId: ledger.ledgerId, ...row })}\n`, { mode: 0o600 });
240
+ return { ledger, paths, identity };
241
+ }
242
+
243
+ export function formatTaskProjectLedgerContext({ ledger, paths, platform = 'unknown', phase = 'unknown' } = {}) {
244
+ const missing = readinessMissingGates(ledger);
245
+ return [
246
+ '--- ARIA TASK/PROJECT LEDGER CONTRACT ---',
247
+ `version: ${TASK_PROJECT_LEDGER_VERSION}`,
248
+ `ledger_id: ${ledger?.ledgerId || 'unknown'}`,
249
+ `platform: ${platform}`,
250
+ `phase: ${phase}`,
251
+ `project_ref: ${ledger?.identity?.projectHint || 'unknown'}`,
252
+ `task_ref: ${ledger?.identity?.taskHint || 'unknown'}`,
253
+ `ledger_event_count: ${Array.isArray(ledger?.phaseEvents) ? ledger.phaseEvents.length : 0}`,
254
+ `readiness_claim_allowed: ${ledger?.controls?.readinessClaimAllowed === true ? 'true' : 'false'}`,
255
+ `missing_readiness_gates: ${missing.length > 0 ? missing.join(', ') : 'none'}`,
256
+ `ledger_path: ${paths?.ledgerPath || 'unknown'}`,
257
+ 'required_behavior: create/update this ledger for every task and project; use it as the controlling anti-drift artifact before tool use, final claims, and long-running handoff.',
258
+ 'claim_rule: do not say done/complete/ready/verified/fixed/shipped unless this ledger has verification evidence and no open blockers.',
259
+ 'quality_rule: every phase must preserve intent, scope, evidence, verification, recovery, and post-turn writeback where applicable.',
260
+ '--- /ARIA TASK/PROJECT LEDGER CONTRACT ---',
261
+ ].join('\n');
262
+ }
263
+
264
+ export function evaluateTaskProjectClaim({ text = '', ledger = {} } = {}) {
265
+ const claim = CLAIM_RX.test(String(text || ''));
266
+ if (!claim) return { ok: true, claim: false, missingReadinessGates: [], openBlockers: [] };
267
+ const missingReadinessGates = readinessMissingGates(ledger);
268
+ const openBlockers = Array.isArray(ledger.openBlockers) ? ledger.openBlockers : [];
269
+ return {
270
+ ok: missingReadinessGates.length === 0 && openBlockers.length === 0 && ledger.controls?.readinessClaimAllowed === true,
271
+ claim: true,
272
+ missingReadinessGates,
273
+ openBlockers,
274
+ };
275
+ }
276
+
277
+ export function recordBlockedTaskProjectClaim({ ledger, paths, text, evaluation } = {}) {
278
+ if (!ledger || !paths?.ledgerPath) return null;
279
+ const now = new Date().toISOString();
280
+ ledger.blockedClaims = Array.isArray(ledger.blockedClaims) ? ledger.blockedClaims : [];
281
+ ledger.blockedClaims.push({
282
+ at: now,
283
+ textHash: stableHash(text || ''),
284
+ missingReadinessGates: evaluation?.missingReadinessGates || [],
285
+ openBlockerCount: evaluation?.openBlockers?.length || 0,
286
+ });
287
+ ledger.updatedAt = now;
288
+ writeJsonAtomic(paths.ledgerPath, ledger);
289
+ return ledger.blockedClaims[ledger.blockedClaims.length - 1];
290
+ }
@@ -2,12 +2,14 @@
2
2
  * Aria Harness Gate — pre-tool cognition enforcement for OpenCode.
3
3
  * Routes through HTTPHarnessClient SDK for substrate-backed validation.
4
4
  */
5
- import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
5
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
6
6
  import { createHash } from 'node:crypto';
7
7
  import { spawnSync } from 'node:child_process';
8
8
  import { homedir } from 'node:os';
9
- import { join } from 'node:path';
9
+ import { dirname, join } from 'node:path';
10
10
  import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.js';
11
+ import { updateTaskProjectLedger } from '../harness-context/task-project-ledger.mjs';
12
+ import { resolveAriaAuthToken } from '../harness-context/auth-token.mjs';
11
13
 
12
14
  const HOME = homedir();
13
15
  const SDK_CANDIDATES = [
@@ -15,11 +17,12 @@ const SDK_CANDIDATES = [
15
17
  join(HOME, '.codex', 'aria-sdk', 'index.js'),
16
18
  join(HOME, '.claude', 'aria-sdk', 'index.js'),
17
19
  ];
18
- const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
19
- const LICENSE_PATH = join(HOME, '.aria', 'license.json');
20
20
  const GOVERNANCE_GATE_PATH = join(HOME, '.aria', 'bin', 'aria-governance-gate');
21
21
  const RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
22
22
  const RECEIPT_DIR = join(HOME, '.opencode', 'aria-mizan-receipts');
23
+ const GATEOFF_MARKER_PATH = join(HOME, '.aria', 'gates-off.manual');
24
+ const GATEOFF_ENV_RX = /^(?:1|true|yes|on)$/i;
25
+ const GATEOFF_PHRASE_RX = /\bGATEOFF\b/;
23
26
 
24
27
  const DESTRUCTIVE_PATTERNS = [
25
28
  { rx: /(?:^|[;&|]\s*|\$\(\s*|`\s*)sudo\s+\S/, name: 'sudo' },
@@ -63,6 +66,34 @@ let _clientError = null;
63
66
  let _lastMizanReceipt = null;
64
67
  let _lastActionSummary = '';
65
68
 
69
+ function textFromPayload(payload) {
70
+ if (!payload) return '';
71
+ if (typeof payload === 'string') return payload;
72
+ try { return JSON.stringify(payload); } catch { return String(payload); }
73
+ }
74
+
75
+ function persistEmergencyGateOff(reason = 'phrase') {
76
+ mkdirSync(dirname(GATEOFF_MARKER_PATH), { recursive: true, mode: 0o700 });
77
+ writeFileSync(GATEOFF_MARKER_PATH, JSON.stringify({
78
+ disabled: true,
79
+ reason,
80
+ disabledAt: new Date().toISOString(),
81
+ reenable: 'Manual owner action only: remove this marker and unset ARIA_GATES_OFF/ARIA_HARNESS_GATES_OFF.',
82
+ }, null, 2) + '\n', { mode: 0o600 });
83
+ }
84
+
85
+ function emergencyGateOffDecision(payload = '') {
86
+ if (GATEOFF_ENV_RX.test(String(process.env.ARIA_GATES_OFF || process.env.ARIA_HARNESS_GATES_OFF || ''))) {
87
+ return { off: true, reason: 'env' };
88
+ }
89
+ if (existsSync(GATEOFF_MARKER_PATH)) return { off: true, reason: 'marker' };
90
+ if (GATEOFF_PHRASE_RX.test(textFromPayload(payload))) {
91
+ persistEmergencyGateOff('phrase');
92
+ return { off: true, reason: 'phrase' };
93
+ }
94
+ return { off: false, reason: null };
95
+ }
96
+
66
97
  function receiptPath(sessionId) {
67
98
  return join(RECEIPT_DIR, `${String(sessionId || 'opencode').replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
68
99
  }
@@ -109,29 +140,22 @@ function runUniversalGovernanceGate(payload) {
109
140
  if (child.status !== 0 || result?.ok === false || result?.decision === 'block') {
110
141
  throw new Error(
111
142
  `=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===\n\n` +
112
- `${stdout || child.stderr || 'aria-governance-gate blocked this action.'}`
143
+ `${stdout || child.stderr || 'aria-governance-gate blocked this action.'}\n\n` +
144
+ 'Recovery contract:\n' +
145
+ '1. Do not retry the same blocked action unchanged.\n' +
146
+ '2. If this is post-compaction, emit post_compact_continuation with current objective, known blockers, next executable step, what not to touch, and verification already run.\n' +
147
+ '3. If this is emergency owner recovery, emit GATEOFF once or set ARIA_GATES_OFF=1, then manually re-enable after recovery.\n' +
148
+ '4. Otherwise load the named skills, re-write with <applied_cognition>, and re-test before retrying.'
113
149
  );
114
150
  }
151
+ if (result?.decision === 'warn' || result?.governanceMode === 'recovery-required' || result?.governanceMode === 'architectural-intervention-required') {
152
+ process.stderr.write(`[aria-governance] ${result.governanceMode || 'recovery-required'}\n`);
153
+ }
115
154
  return result;
116
155
  }
117
156
 
118
157
  function resolveToken() {
119
- if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
120
- if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
121
- if (process.env.ARIA_MASTER_TOKEN) return process.env.ARIA_MASTER_TOKEN;
122
- try {
123
- if (existsSync(OWNER_TOKEN_PATH)) {
124
- const v = readFileSync(OWNER_TOKEN_PATH, 'utf8').trim();
125
- if (v) return v;
126
- }
127
- } catch {}
128
- try {
129
- if (existsSync(LICENSE_PATH)) {
130
- const j = JSON.parse(readFileSync(LICENSE_PATH, 'utf8'));
131
- if (j.token) return j.token;
132
- }
133
- } catch {}
134
- return '';
158
+ return resolveAriaAuthToken({ baseUrl: resolveBaseUrl() }).token;
135
159
  }
136
160
 
137
161
  function resolveBaseUrl() {
@@ -206,6 +230,10 @@ export default async function HarnessGatePlugin(ctx) {
206
230
  return {
207
231
  'tool.execute.before': async (input, output) => {
208
232
  const args = output?.args ?? input.args ?? {};
233
+ const emergencyGateOff = emergencyGateOffDecision({ input, output, args });
234
+ if (emergencyGateOff.off) {
235
+ return;
236
+ }
209
237
  const rawToolName = String(input.tool || input.name || input.type || '');
210
238
  const normalizedToolName = rawToolName.toLowerCase();
211
239
  const toolName = normalizedToolName.includes('bash') ? 'Bash'
@@ -235,6 +263,13 @@ export default async function HarnessGatePlugin(ctx) {
235
263
  const action = toolName === 'Bash' ? 'bash' : 'edit';
236
264
  const target = toolName === 'Bash' ? cmd.slice(0, 200) : filePath.slice(0, 200);
237
265
  const actionRef = makeEvidenceRef('opencode_tool_request', { toolName, action, target }, { sessionId, label });
266
+ updateTaskProjectLedger({
267
+ platform: 'opencode',
268
+ phase: 'pre_tool',
269
+ source: 'opencode-harness-gate',
270
+ event: { ...input, sessionId, cwd: process.cwd() },
271
+ evidence: { action_ref: actionRef },
272
+ });
238
273
  runUniversalGovernanceGate({
239
274
  sessionId,
240
275
  sourceRuntime: 'opencode',
@@ -258,11 +293,10 @@ export default async function HarnessGatePlugin(ctx) {
258
293
  isMutation: Boolean(isFileMutation),
259
294
  autoLoadAvailable: false,
260
295
  });
261
- if (!skillGate.ok) {
296
+ if (!skillGate.ok && !skillGate.redirectOnly) {
262
297
  throw new Error(
263
298
  `=== ARIA SKILL AUTOLOAD GATE: ${label} ===\n\n` +
264
- `${formatSkillGateBlock(skillGate)}\n\n` +
265
- `Required next step: call the skill loader for ${skillGate.missingSkills.join(', ')} before retrying this tool.`
299
+ `${formatSkillGateBlock(skillGate)}`
266
300
  );
267
301
  }
268
302
  const rationale =
@@ -309,7 +343,15 @@ export default async function HarnessGatePlugin(ctx) {
309
343
  `=== ARIA MIZAN PRE BLOCK: ${label} ===\n\n` +
310
344
  `${(pre.result?.notes || ['Mizan pre-phase blocked this action.']).slice(0, 4).join('\n')}\n\n` +
311
345
  `Required packs: ${(pre.operatorPlan?.requiredPacks || []).join(', ')}\n` +
312
- `Required operators: ${(pre.operatorPlan?.priorityOperators || []).join(', ')}`
346
+ `Required operators: ${(pre.operatorPlan?.priorityOperators || []).join(', ')}\n\n` +
347
+ `TEACHING:\n` +
348
+ `- Mizan pre blocks mean the action shape failed before execution; do not force the tool through.\n` +
349
+ `- The retry must add cognition, proof, and an expected predicate for this exact action.\n\n` +
350
+ `Recovery contract:\n` +
351
+ `1. Do not retry the same blocked action unchanged.\n` +
352
+ `2. Re-write the action with inline cognition or a <cognition> block.\n` +
353
+ `3. Add <verify> for deploy/shared-infra actions and <expected> for non-trivial actions.\n` +
354
+ `4. Re-test by submitting the revised tool call through this same gate.`
313
355
  );
314
356
  }
315
357
  if (_lastMizanReceipt?.receiptId && _lastActionSummary && _lastActionSummary !== cmdPreview) {
@@ -349,14 +391,23 @@ export default async function HarnessGatePlugin(ctx) {
349
391
  `Reason: ${check.reason || 'tool lane contention'}\n` +
350
392
  `Job: ${check.queue?.jobId || 'unknown'} (${check.queue?.status || 'queued'})\n` +
351
393
  `Queue depth: ${check.queue?.queueDepth ?? 'unknown'}\n\n` +
352
- `Recovery: retry after overlapping Hive lease expires, or let the tool-lane worker claim the queued job.`
394
+ `Recovery contract:\n` +
395
+ `1. Do not retry immediately in a tight loop.\n` +
396
+ `2. Wait for the overlapping Hive lease to expire or let the tool-lane worker claim the queued job.\n` +
397
+ `3. Re-test by submitting the same action only after the lease/queue state changes.`
353
398
  );
354
399
  }
355
400
  throw new Error(
356
401
  `=== ARIA SD GATE: ${label} ===\n\n` +
357
402
  `Blocked by substrate gate: ${check.reason || 'no reason provided'}\n` +
358
403
  `Required gates: ${(check.requiredGates || []).join(', ')}\n\n` +
359
- `Complete a <cognition> block or inline cognition comments before this action.`
404
+ `TEACHING:\n` +
405
+ `- Substrate gates block when the action lacks required proof or cognition for its risk class.\n\n` +
406
+ `Recovery contract:\n` +
407
+ `1. Do not retry the same blocked action unchanged.\n` +
408
+ `2. Complete a <cognition> block or inline cognition comments before this action.\n` +
409
+ `3. Add any required gates listed above, especially verify evidence for high-risk actions.\n` +
410
+ `4. Re-test by submitting the revised action through this same gate.`
360
411
  );
361
412
  }
362
413
  return;
@@ -379,6 +430,15 @@ export default async function HarnessGatePlugin(ctx) {
379
430
  ' # tafakkur: [reflection — second-order effects, ≥20 chars]',
380
431
  '',
381
432
  `${deploy ? 'DEPLOY requires a <verify> block with target, verified, rollback, axiom fields.\n' : ''}`,
433
+ 'TEACHING:',
434
+ '- Local fallback blocks because the SDK path was unavailable or declined to allow without local cognition proof.',
435
+ '- The safe retry changes the action shape; it does not bypass the gate.',
436
+ '',
437
+ 'Recovery contract:',
438
+ '1. Do not retry the same blocked action unchanged.',
439
+ '2. Add the inline cognition comments shown above.',
440
+ '3. Add <verify> for deploy/shared-infra actions and <expected> for non-trivial actions.',
441
+ '4. Re-test by submitting the revised action through this same gate.',
382
442
  `Run \`aria connect\` to install the full SDK-backed gate (substrate checkAction).`,
383
443
  ].filter(Boolean).join('\n');
384
444
  throw new Error(msg);
@@ -1,14 +1 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { tmpdir } from 'node:os';
4
- const RECEIPT_ROOT = process.env.ARIA_SKILL_RECEIPT_DIR || join(tmpdir(), 'aria-skill-receipts');
5
- const ALIASES = new Map([['deploy', 'aria-harness-deploy'], ['output', 'aria-harness-output-discipline'], ['repo', 'aria-repo-doctrine'], ['forge', 'aria-forge-guardrails']]);
6
- const RX = { deploy: /deploy-service\.sh|kubectl\s+(?:apply|set|rollout|delete|scale)|helm\s+upgrade|terraform\s+apply|docker\s+push/i, mutationTool: /^(?:edit|write|notebookedit|patch|apply_patch)$/i, mutation: /apply_patch|write file|edit file|modify|delete file|migration|handler|route|runtime|hook|plugin|\btest\b|smoke script/i, strip: /remove|delete|strip|drop|omit|disable|bypass|skip|stub|mock|fake|placeholder|temporary|quick scaffold|band-aid/i, readiness: /production-ready|ready for production|works in general|general readiness|client packages?|npm packages?|SDKs?|runtimes?|harnesses?|release-ready|ship-ready/i, narrow: /single flow|one flow|narrow e2e|covered flow|specific path|widget flow/i, completion: /done|complete|completed|ready|verified|fixed|shipped|implemented|production-ready/i, badProof: /but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped|unresolved|follow-up|failed|failing|error|red|not run|could not verify|untested|no proof|missing proof|without proof/i, advisory: /non-blocking|warning only|warn only|advisory|fall through|falls through|fail open|soft fail|logged and continue|quality gate warning/i, success: /(?:verified|passed|success|successful|green|ok)\s*[:=\-].{0,120}(?:npm|node|playwright|jest|vitest|build|test|lint|typecheck|curl|kubectl|self-test|e2e|probe|smoke)|(?:npm|node|playwright|jest|vitest|build|test|lint|typecheck|curl|kubectl).{0,120}(?:passed|success|successful|green|exit\s*0)/i, resubmit: /re-?submission|resubmit/i, rewrite: /re-?write|rewrite|fix/i, retest: /re-?test|retest|rerun/i, aria: /ARIA console|Aria console|\/chat|aria-pipeline-mcp|aria_chat|escalat(?:e|ion).{0,80}ARIA/i };
7
- function normalizeSkillName(skill) { return ALIASES.get(String(skill || '').trim()) || String(skill || '').trim(); }
8
- function sessionDir(sessionId) { return join(RECEIPT_ROOT, encodeURIComponent(String(sessionId || 'unknown'))); }
9
- function readReceiptSkills(sessionId) { const dir = sessionDir(sessionId); if (!existsSync(dir)) return new Set(); const skills = new Set(); for (const name of readdirSync(dir)) { if (!name.endsWith('.json')) continue; try { const receipt = JSON.parse(readFileSync(join(dir, name), 'utf8')); if (receipt?.skill) skills.add(normalizeSkillName(receipt.skill)); } catch {} } return skills; }
10
- function readInlineSkills(text) { const skills = new Set(); const value = String(text || ''); for (const match of value.matchAll(/<skill_content\s+name=["']([^"']+)["']/gi)) skills.add(normalizeSkillName(match[1])); return skills; }
11
- export function recordSkillLoaded({ sessionId, skill, surface = 'unknown', metadata = {} } = {}) { const normalized = normalizeSkillName(skill); if (!normalized) throw new Error('recordSkillLoaded requires a skill name'); const dir = sessionDir(sessionId); mkdirSync(dir, { recursive: true }); const receipt = { skill: normalized, surface, metadata, recordedAt: new Date().toISOString() }; writeFileSync(join(dir, `${encodeURIComponent(normalized)}.json`), `${JSON.stringify(receipt, null, 2)}\n`); return receipt; }
12
- export function classifyRequiredSkills({ text = '', action = '', toolName = '', filePath = '', isDeploy = false, isMutation = false, isOutputCloseout = false } = {}) { const combined = [text, action, toolName, filePath].filter(Boolean).join('\n'); const required = new Set(); const reasons = []; const recoveryMissing = []; if (isDeploy || RX.deploy.test(combined)) { required.add('aria-harness-deploy'); required.add('aria-forge-guardrails'); reasons.push('deploy/shared-infrastructure action requires fail-closed deploy and forge guardrails'); } if (isMutation || RX.mutationTool.test(toolName)) { required.add('aria-repo-doctrine'); reasons.push('repository/runtime mutation requires repo doctrine'); } if (RX.strip.test(combined)) { required.add('aria-harness-no-stripping'); reasons.push('strip/remove/bypass language requires no-stripping gate'); } if (isOutputCloseout && RX.completion.test(combined)) { required.add('aria-harness-output-discipline'); reasons.push('owner-facing completion/readiness claim requires output discipline'); if (!RX.success.test(combined)) recoveryMissing.push('successful proof from a concrete command/probe'); } if (RX.readiness.test(combined)) { required.add('architecture-decision'); required.add('testing-strategy'); required.add('aria-forge-guardrails'); required.add('aria-harness-output-discipline'); reasons.push('broad production/package/SDK/runtime readiness claim requires architecture, testing, and forge guardrails'); } if (RX.readiness.test(combined) && RX.narrow.test(combined)) { required.add('testing-strategy'); required.add('aria-forge-guardrails'); reasons.push('narrow e2e proof cannot support broad readiness claim without readiness-matrix discipline'); } if (RX.completion.test(combined) && RX.badProof.test(combined)) { required.add('aria-harness-output-discipline'); required.add('aria-forge-guardrails'); reasons.push('completion claim with unresolved or failed proof requires recovery cycle'); if (!RX.resubmit.test(combined)) recoveryMissing.push('re-submission'); if (!RX.rewrite.test(combined)) recoveryMissing.push('re-write'); if (!RX.retest.test(combined)) recoveryMissing.push('re-test'); if (!RX.aria.test(combined)) recoveryMissing.push('ARIA console escalation'); } if (RX.advisory.test(combined)) { required.add('aria-forge-guardrails'); required.add('aria-harness-output-discipline'); reasons.push('advisory/fail-open gate language requires fail-closed hardening discipline'); } return { requiredSkills: [...required].sort(), reasons, recoveryMissing }; }
13
- export function evaluateSkillGate(options = {}) { const classified = classifyRequiredSkills(options); const text = [options.text, options.action].filter(Boolean).join('\n'); const loaded = new Set([...readReceiptSkills(options.sessionId), ...readInlineSkills(text)]); const missingSkills = classified.requiredSkills.filter((skill) => !loaded.has(skill)); const recoveryMissing = classified.recoveryMissing || []; return { ok: missingSkills.length === 0 && recoveryMissing.length === 0, surface: options.surface || 'unknown', sessionId: options.sessionId || 'unknown', requiredSkills: classified.requiredSkills, loadedSkills: [...loaded].sort(), missingSkills, recoveryMissing, reasons: classified.reasons, autoLoadAvailable: options.autoLoadAvailable === true }; }
14
- export function formatSkillGateBlock(result = {}) { const missing = Array.isArray(result.missingSkills) ? result.missingSkills : []; const recovery = Array.isArray(result.recoveryMissing) ? result.recoveryMissing : []; const reasons = Array.isArray(result.reasons) ? result.reasons : []; return ['=== ARIA SKILL AUTOLOAD GATE BLOCK ===', `surface: ${result.surface || 'unknown'}`, `missing_skills: ${missing.length ? missing.join(', ') : '(none)'}`, `missing_recovery_cycle: ${recovery.length ? recovery.join(', ') : '(none)'}`, `required_skills: ${(result.requiredSkills || []).join(', ') || '(none)'}`, reasons.length ? `reasons: ${reasons.join(' | ')}` : 'reasons: no classifier reason recorded', 'counter_action: re-submit, re-write, re-test, and escalate through ARIA console until successful proof exists. Do not downgrade this to an advisory warning.'].join('\n'); }
1
+ export * from '../../../hooks/lib/skill-autoload-gate.mjs';
@@ -2,10 +2,12 @@
2
2
  * Aria Harness Outcome — post-tool recording via HTTPHarnessClient SDK.
3
3
  * Routes through the canonical SDK for outcome/garden/ledger writes.
4
4
  */
5
- import { existsSync, readFileSync } from 'node:fs';
5
+ import { existsSync } from 'node:fs';
6
6
  import { createHash } from 'node:crypto';
7
7
  import { homedir } from 'node:os';
8
8
  import { join } from 'node:path';
9
+ import { updateTaskProjectLedger } from '../harness-context/task-project-ledger.mjs';
10
+ import { resolveAriaAuthToken } from '../harness-context/auth-token.mjs';
9
11
 
10
12
  const HOME = homedir();
11
13
  const SDK_CANDIDATES = [
@@ -13,29 +15,13 @@ const SDK_CANDIDATES = [
13
15
  join(HOME, '.codex', 'aria-sdk', 'index.js'),
14
16
  join(HOME, '.claude', 'aria-sdk', 'index.js'),
15
17
  ];
16
- const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
17
- const LICENSE_PATH = join(HOME, '.aria', 'license.json');
18
18
  const RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
19
19
 
20
20
  let _client = null;
21
21
  let _clientError = null;
22
22
 
23
23
  function resolveToken() {
24
- if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
25
- if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
26
- try {
27
- if (existsSync(OWNER_TOKEN_PATH)) {
28
- const v = readFileSync(OWNER_TOKEN_PATH, 'utf8').trim();
29
- if (v) return v;
30
- }
31
- } catch {}
32
- try {
33
- if (existsSync(LICENSE_PATH)) {
34
- const j = JSON.parse(readFileSync(LICENSE_PATH, 'utf8'));
35
- if (j.token) return j.token;
36
- }
37
- } catch {}
38
- return '';
24
+ return resolveAriaAuthToken({ baseUrl: resolveBaseUrl() }).token;
39
25
  }
40
26
 
41
27
  function resolveBaseUrl() {
@@ -61,6 +47,19 @@ function makeEvidenceRef(kind, value, metadata = {}) {
61
47
  };
62
48
  }
63
49
 
50
+ function looksLikeVerification(input, output, success) {
51
+ if (!success) return false;
52
+ const text = [
53
+ input?.args?.command,
54
+ output?.stdout,
55
+ output?.stderr,
56
+ output?.text,
57
+ output?.output,
58
+ output?.message,
59
+ ].map((value) => typeof value === 'string' ? value : '').join('\n');
60
+ return /\b(?:npm\s+run\s+(?:check|test|build|lint|typecheck)|(?:npx\s+)?(?:jest|vitest|tsc|eslint)|check:|test:|build:|passed|exit\s*0|0\s*failures?)\b/i.test(text);
61
+ }
62
+
64
63
  async function releaseHiveLease(sessionId, files) {
65
64
  const token = resolveToken();
66
65
  if (!token || !Array.isArray(files) || files.length === 0) return;
@@ -115,6 +114,17 @@ export default async function HarnessOutcomePlugin(ctx) {
115
114
  success,
116
115
  output: output ?? null,
117
116
  }, { sessionId });
117
+ updateTaskProjectLedger({
118
+ platform: 'opencode',
119
+ phase: 'post_tool',
120
+ source: 'opencode-harness-outcome',
121
+ event: { ...input, sessionId, cwd: process.cwd() },
122
+ evidence: {
123
+ outcome_ref: outcomeRef,
124
+ verification: looksLikeVerification(input, output, success),
125
+ commandResult: success ? 'success' : 'failed',
126
+ },
127
+ });
118
128
 
119
129
  // Try SDK first
120
130
  const client = await getClient();
@@ -141,12 +151,7 @@ export default async function HarnessOutcomePlugin(ctx) {
141
151
  process.env.ARIA_HARNESS_BASE_URL ||
142
152
  process.env.ARIA_HARNESS_URL ||
143
153
  'https://harness.ariasos.com';
144
- let harnessToken = process.env.ARIA_HARNESS_TOKEN || process.env.ARIA_API_KEY || '';
145
- try {
146
- if (!harnessToken) {
147
- if (existsSync(OWNER_TOKEN_PATH)) harnessToken = readFileSync(OWNER_TOKEN_PATH, 'utf8').trim();
148
- }
149
- } catch {}
154
+ const harnessToken = resolveAriaAuthToken({ baseUrl: harnessUrl }).token;
150
155
  if (!harnessToken) return;
151
156
 
152
157
  fetch(`${harnessUrl}/api/harness/outcome-record`, {