@aria_asi/cli 0.2.30 → 0.2.32

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 (95) hide show
  1. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  2. package/dist/aria-connector/src/connectors/claude-code.js +115 -20
  3. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  4. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  5. package/dist/aria-connector/src/connectors/codex.js +551 -11
  6. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  7. package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts +7 -0
  8. package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts.map +1 -0
  9. package/dist/aria-connector/src/connectors/doctrine-trigger-map.js +87 -0
  10. package/dist/aria-connector/src/connectors/doctrine-trigger-map.js.map +1 -0
  11. package/dist/aria-connector/src/connectors/must-read.d.ts +4 -0
  12. package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -0
  13. package/dist/aria-connector/src/connectors/must-read.js +115 -0
  14. package/dist/aria-connector/src/connectors/must-read.js.map +1 -0
  15. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  16. package/dist/aria-connector/src/connectors/opencode.js +27 -9
  17. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  18. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
  19. package/dist/aria-connector/src/connectors/runtime.js +231 -19
  20. package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
  21. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  22. package/dist/aria-connector/src/connectors/shell.js +76 -3
  23. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  24. package/dist/assets/hooks/aria-agent-handoff.mjs +23 -0
  25. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +121 -28
  26. package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
  27. package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
  28. package/dist/assets/hooks/aria-pre-tool-gate.mjs +383 -93
  29. package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
  30. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +93 -16
  31. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +33 -1
  32. package/dist/assets/hooks/aria-stop-gate.mjs +346 -81
  33. package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
  34. package/dist/assets/hooks/lib/canonical-lenses.mjs +6 -5
  35. package/dist/assets/hooks/lib/gate-loop-state.mjs +50 -0
  36. package/dist/assets/hooks/lib/hook-message-window.mjs +121 -0
  37. package/dist/assets/hooks/test-tier-lens-labeling.mjs +26 -58
  38. package/dist/assets/opencode-plugins/harness-gate/index.js +40 -5
  39. package/dist/assets/opencode-plugins/harness-stop/index.js +133 -10
  40. package/dist/runtime/auth-middleware.mjs +251 -0
  41. package/dist/runtime/codex-bridge.mjs +644 -0
  42. package/dist/runtime/discipline/CLAUDE.md +28 -0
  43. package/dist/runtime/discipline/doctrine_trigger_map.json +534 -0
  44. package/dist/runtime/doctrine_trigger_map.json +534 -0
  45. package/dist/runtime/fleet-engine.mjs +231 -0
  46. package/dist/runtime/harness-daemon.mjs +460 -0
  47. package/dist/runtime/manifest.json +1 -1
  48. package/dist/runtime/metering.mjs +100 -0
  49. package/dist/runtime/onboarding-engine.mjs +89 -0
  50. package/dist/runtime/plugin-engine.mjs +196 -0
  51. package/dist/runtime/sdk/BUNDLED.json +1 -1
  52. package/dist/runtime/sdk/index.d.ts +12 -0
  53. package/dist/runtime/sdk/index.js +120 -14
  54. package/dist/runtime/sdk/index.js.map +1 -1
  55. package/dist/runtime/service.mjs +1140 -48
  56. package/dist/runtime/workflow-engine.mjs +322 -0
  57. package/dist/sdk/BUNDLED.json +1 -1
  58. package/dist/sdk/index.d.ts +12 -0
  59. package/dist/sdk/index.js +120 -14
  60. package/dist/sdk/index.js.map +1 -1
  61. package/hooks/aria-agent-handoff.mjs +23 -0
  62. package/hooks/aria-cognition-substrate-binding.mjs +121 -28
  63. package/hooks/aria-harness-via-sdk.mjs +126 -12
  64. package/hooks/aria-pre-emit-dryrun.mjs +35 -0
  65. package/hooks/aria-pre-tool-gate.mjs +383 -93
  66. package/hooks/aria-preprompt-consult.mjs +28 -2
  67. package/hooks/aria-preturn-memory-gate.mjs +93 -16
  68. package/hooks/aria-repo-doctrine-gate.mjs +33 -1
  69. package/hooks/aria-stop-gate.mjs +346 -81
  70. package/hooks/doctrine_trigger_map.json +55 -0
  71. package/hooks/lib/canonical-lenses.mjs +6 -5
  72. package/hooks/lib/gate-loop-state.mjs +50 -0
  73. package/hooks/lib/hook-message-window.mjs +121 -0
  74. package/hooks/test-tier-lens-labeling.mjs +26 -58
  75. package/opencode-plugins/harness-gate/index.js +40 -5
  76. package/opencode-plugins/harness-stop/index.js +133 -10
  77. package/package.json +1 -1
  78. package/runtime-src/auth-middleware.mjs +251 -0
  79. package/runtime-src/codex-bridge.mjs +644 -0
  80. package/runtime-src/fleet-engine.mjs +231 -0
  81. package/runtime-src/harness-daemon.mjs +460 -0
  82. package/runtime-src/metering.mjs +100 -0
  83. package/runtime-src/onboarding-engine.mjs +89 -0
  84. package/runtime-src/plugin-engine.mjs +196 -0
  85. package/runtime-src/service.mjs +1140 -48
  86. package/runtime-src/workflow-engine.mjs +322 -0
  87. package/scripts/bundle-sdk.mjs +5 -0
  88. package/src/connectors/claude-code.ts +126 -20
  89. package/src/connectors/codex.ts +559 -10
  90. package/src/connectors/doctrine-trigger-map.ts +112 -0
  91. package/src/connectors/must-read.ts +117 -0
  92. package/src/connectors/opencode.ts +28 -9
  93. package/src/connectors/runtime.ts +241 -21
  94. package/src/connectors/shell.ts +78 -3
  95. package/dist/cli-0.2.0.tgz +0 -0
@@ -0,0 +1,322 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { randomUUID } from 'node:crypto';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const STATE_DIR = join(__dirname, '..', 'state');
8
+
9
+ function ensureDir(p) {
10
+ if (!existsSync(p)) mkdirSync(p, { recursive: true });
11
+ }
12
+
13
+ function workflowsPath(tenantId) {
14
+ return join(STATE_DIR, `workflows-${tenantId}.json`);
15
+ }
16
+
17
+ const WORKFLOW_TEMPLATES = {
18
+ 'lead-qualification-pipeline': {
19
+ id: 'lead-qualification-pipeline',
20
+ archetype: 'leadQualifier',
21
+ name: 'Lead Qualification Pipeline',
22
+ description: 'Receive inbound lead, classify, enrich data, score, and route to appropriate agent',
23
+ steps: [
24
+ { name: 'receive_and_classify', type: 'receive', description: 'Receive lead input and classify by source and intent' },
25
+ { name: 'enrich_data', type: 'enrich', description: 'Look up additional data points for the lead' },
26
+ { name: 'score_and_route', type: 'score', description: 'Score the lead and determine routing' },
27
+ { name: 'approval_gate', type: 'approve', description: 'Confirm routing decision (auto-approve if score above threshold)', autoApprove: { field: 'score', operator: '>=', value: 80 } },
28
+ { name: 'execute_routing', type: 'execute', description: 'Route lead to assigned agent or pipeline' },
29
+ { name: 'log_and_notify', type: 'log', description: 'Log the qualification result and notify relevant agents' },
30
+ ],
31
+ escalation: { to: 'directorOfSales', condition: 'score >= 90' },
32
+ retryPolicy: { maxAttempts: 3, backoffMs: 5000 },
33
+ },
34
+ 'deal-evaluation-pipeline': {
35
+ id: 'deal-evaluation-pipeline',
36
+ archetype: 'dealAnalyzer',
37
+ name: 'Deal Evaluation Pipeline',
38
+ description: 'Collect deal data, compare terms, assess risk, and provide recommendation',
39
+ steps: [
40
+ { name: 'collect_deal_data', type: 'receive', description: 'Gather all deal parameters and context' },
41
+ { name: 'compare_terms', type: 'enrich', description: 'Compare against historical deals and market benchmarks' },
42
+ { name: 'risk_assessment', type: 'score', description: 'Evaluate financial, operational, and strategic risks' },
43
+ { name: 'recommend', type: 'execute', description: 'Generate go/no-go recommendation with rationale' },
44
+ ],
45
+ escalation: { to: 'directorOfFinance', condition: 'risk_level == high' },
46
+ retryPolicy: { maxAttempts: 2, backoffMs: 10000 },
47
+ },
48
+ 'outreach-sequence': {
49
+ id: 'outreach-sequence',
50
+ archetype: 'outboundCloser',
51
+ name: 'Outreach Sequence',
52
+ description: 'Target selection, initial contact, follow-up, objection handling, and close',
53
+ steps: [
54
+ { name: 'target_selection', type: 'receive', description: 'Identify target prospects based on criteria' },
55
+ { name: 'initial_contact', type: 'execute', description: 'Send initial outreach message' },
56
+ { name: 'follow_up', type: 'execute', description: 'Send follow-up based on prospect engagement' },
57
+ { name: 'objection_handle', type: 'score', description: 'Address objections and refine approach' },
58
+ { name: 'close', type: 'execute', description: 'Attempt to close or schedule next action' },
59
+ ],
60
+ escalation: { to: 'directorOfSales', condition: 'stage == stalled > 3 days' },
61
+ retryPolicy: { maxAttempts: 5, backoffMs: 86400000 },
62
+ },
63
+ 'content-calendar': {
64
+ id: 'content-calendar',
65
+ archetype: 'contentStrategist',
66
+ name: 'Content Calendar',
67
+ description: 'Plan content topics, draft, review, publish, and measure performance',
68
+ steps: [
69
+ { name: 'plan_topics', type: 'receive', description: 'Generate content calendar based on strategy and trends' },
70
+ { name: 'draft_content', type: 'execute', description: 'Create initial content draft' },
71
+ { name: 'review', type: 'approve', description: 'Review for quality, brand alignment, and compliance' },
72
+ { name: 'publish', type: 'execute', description: 'Publish approved content to designated channels' },
73
+ { name: 'measure', type: 'score', description: 'Track performance metrics and feed back into planning' },
74
+ ],
75
+ escalation: { to: 'directorOfMarketing', condition: 'engagement < threshold' },
76
+ retryPolicy: { maxAttempts: 2, backoffMs: 0 },
77
+ },
78
+ 'deal-lifecycle': {
79
+ id: 'deal-lifecycle',
80
+ archetype: 'transactionCoordinator',
81
+ name: 'Deal Lifecycle',
82
+ description: 'Initiate deal, track milestones, coordinate stakeholders, verify, and close',
83
+ steps: [
84
+ { name: 'initiate', type: 'receive', description: 'Create deal record and assign stakeholders' },
85
+ { name: 'track_milestones', type: 'enrich', description: 'Monitor and update milestone progress' },
86
+ { name: 'coordinate', type: 'execute', description: 'Coordinate between parties and resolve blockers' },
87
+ { name: 'verify', type: 'score', description: 'Final verification of all requirements and documents' },
88
+ { name: 'close', type: 'execute', description: 'Execute close and update records' },
89
+ ],
90
+ escalation: { to: 'chiefOfStaff', condition: 'blocker unresolved > 48h' },
91
+ retryPolicy: { maxAttempts: 3, backoffMs: 3600000 },
92
+ },
93
+ 'reporting-cycle': {
94
+ id: 'reporting-cycle',
95
+ archetype: 'financialAnalyst',
96
+ name: 'Reporting Cycle',
97
+ description: 'Collect financial data, reconcile, analyze, forecast, and produce reports',
98
+ steps: [
99
+ { name: 'collect_data', type: 'receive', description: 'Gather financial data from all sources' },
100
+ { name: 'reconcile', type: 'enrich', description: 'Cross-reference and reconcile discrepancies' },
101
+ { name: 'analyze', type: 'score', description: 'Perform variance analysis and trend detection' },
102
+ { name: 'forecast', type: 'enrich', description: 'Generate forward-looking projections' },
103
+ { name: 'produce_report', type: 'execute', description: 'Compile and distribute report' },
104
+ ],
105
+ escalation: { to: 'directorOfFinance', condition: 'variance > 10%' },
106
+ retryPolicy: { maxAttempts: 2, backoffMs: 0 },
107
+ },
108
+ 'audit-trail': {
109
+ id: 'audit-trail',
110
+ archetype: 'compliance-guard',
111
+ name: 'Audit Trail',
112
+ description: 'Monitor activity, flag anomalies, investigate, remediate, and document',
113
+ steps: [
114
+ { name: 'monitor', type: 'receive', description: 'Continuously monitor agent outputs and actions' },
115
+ { name: 'flag', type: 'score', description: 'Flag potential compliance issues or anomalies' },
116
+ { name: 'investigate', type: 'enrich', description: 'Investigate flagged items for root cause' },
117
+ { name: 'remediate', type: 'execute', description: 'Apply corrective actions for confirmed violations' },
118
+ { name: 'document', type: 'log', description: 'Document findings and update compliance records' },
119
+ ],
120
+ escalation: { to: 'chiefOfStaff', condition: 'severity == critical' },
121
+ retryPolicy: { maxAttempts: 3, backoffMs: 5000 },
122
+ },
123
+ };
124
+
125
+ export function listWorkflowTemplates() {
126
+ return Object.values(WORKFLOW_TEMPLATES).map(t => ({
127
+ id: t.id,
128
+ archetype: t.archetype,
129
+ name: t.name,
130
+ description: t.description,
131
+ stepCount: t.steps.length,
132
+ hasApprovalGate: t.steps.some(s => s.type === 'approve'),
133
+ escalation: t.escalation,
134
+ }));
135
+ }
136
+
137
+ export function getWorkflowTemplate(workflowId) {
138
+ return WORKFLOW_TEMPLATES[workflowId] || null;
139
+ }
140
+
141
+ export function configureWorkflow(tenantId, workflowId, config = {}) {
142
+ const template = WORKFLOW_TEMPLATES[workflowId];
143
+ if (!template) return { ok: false, error: `Unknown workflow: ${workflowId}. Available: ${Object.keys(WORKFLOW_TEMPLATES).join(', ')}` };
144
+ const pp = workflowsPath(tenantId);
145
+ let state = existsSync(pp) ? JSON.parse(readFileSync(pp, 'utf-8')) : { configured: [], instances: [] };
146
+ const existing = state.configured.findIndex(w => w.id === workflowId);
147
+ const entry = {
148
+ id: workflowId,
149
+ templateId: workflowId,
150
+ config: {
151
+ agentId: config.agentId || null,
152
+ trigger: config.trigger || { type: 'manual' },
153
+ autoApproveThreshold: config.autoApproveThreshold ?? template.steps.find(s => s.type === 'approve')?.autoApprove?.value ?? 80,
154
+ customSteps: config.customSteps || null,
155
+ escalationOverride: config.escalationOverride || null,
156
+ ...config,
157
+ },
158
+ configuredAt: new Date().toISOString(),
159
+ };
160
+ if (existing >= 0) state.configured[existing] = entry; else state.configured.push(entry);
161
+ ensureDir(dirname(pp));
162
+ writeFileSync(pp, JSON.stringify(state, null, 2));
163
+ return { ok: true, workflow: entry };
164
+ }
165
+
166
+ export function startWorkflow(tenantId, workflowId, initialPayload = {}) {
167
+ const template = WORKFLOW_TEMPLATES[workflowId];
168
+ if (!template) return { ok: false, error: `Unknown workflow: ${workflowId}` };
169
+ const pp = workflowsPath(tenantId);
170
+ let state = existsSync(pp) ? JSON.parse(readFileSync(pp, 'utf-8')) : { configured: [], instances: [] };
171
+ const instance = {
172
+ instanceId: randomUUID(),
173
+ workflowId,
174
+ templateId: workflowId,
175
+ status: 'running',
176
+ currentStep: 0,
177
+ steps: template.steps.map((s, i) => ({
178
+ ...s,
179
+ index: i,
180
+ status: i === 0 ? 'active' : 'pending',
181
+ startedAt: i === 0 ? new Date().toISOString() : null,
182
+ completedAt: null,
183
+ attempts: 0,
184
+ result: null,
185
+ })),
186
+ payload: initialPayload,
187
+ history: [{ event: 'started', at: new Date().toISOString(), step: 0 }],
188
+ createdAt: new Date().toISOString(),
189
+ updatedAt: new Date().toISOString(),
190
+ };
191
+ state.instances.push(instance);
192
+ ensureDir(dirname(pp));
193
+ writeFileSync(pp, JSON.stringify(state, null, 2));
194
+ return { ok: true, instance };
195
+ }
196
+
197
+ export function advanceWorkflow(tenantId, instanceId, stepResult = {}) {
198
+ const pp = workflowsPath(tenantId);
199
+ if (!existsSync(pp)) return { ok: false, error: 'No workflows configured' };
200
+ let state = JSON.parse(readFileSync(pp, 'utf-8'));
201
+ const instance = (state.instances || []).find(i => i.instanceId === instanceId);
202
+ if (!instance) return { ok: false, error: `Instance ${instanceId} not found` };
203
+ if (instance.status !== 'running') return { ok: false, error: `Workflow is ${instance.status}` };
204
+ const current = instance.steps[instance.currentStep];
205
+ if (!current) return { ok: false, error: 'No current step' };
206
+ current.status = 'completed';
207
+ current.completedAt = new Date().toISOString();
208
+ current.result = stepResult;
209
+ instance.history.push({ event: 'step_completed', step: instance.currentStep, result: stepResult, at: new Date().toISOString() });
210
+ const template = WORKFLOW_TEMPLATES[instance.workflowId];
211
+ const nextIdx = instance.currentStep + 1;
212
+ if (nextIdx >= instance.steps.length) {
213
+ instance.status = 'completed';
214
+ instance.currentStep = -1;
215
+ instance.history.push({ event: 'workflow_completed', at: new Date().toISOString() });
216
+ } else {
217
+ const nextStep = instance.steps[nextIdx];
218
+ if (nextStep.type === 'approve') {
219
+ const autoApprove = nextStep.autoApprove || template?.steps[nextIdx]?.autoApprove;
220
+ const shouldAutoApprove = autoApprove && stepResult[autoApprove.field] !== undefined &&
221
+ ((autoApprove.operator === '>=' && stepResult[autoApprove.field] >= autoApprove.value) ||
222
+ (autoApprove.operator === '>' && stepResult[autoApprove.field] > autoApprove.value) ||
223
+ (autoApprove.operator === '==' && stepResult[autoApprove.field] == autoApprove.value));
224
+ if (shouldAutoApprove) {
225
+ nextStep.status = 'completed';
226
+ nextStep.completedAt = new Date().toISOString();
227
+ nextStep.result = { autoApproved: true };
228
+ instance.history.push({ event: 'auto_approved', step: nextIdx, at: new Date().toISOString() });
229
+ const afterNext = nextIdx + 1;
230
+ if (afterNext < instance.steps.length) {
231
+ instance.currentStep = afterNext;
232
+ instance.steps[afterNext].status = 'active';
233
+ instance.steps[afterNext].startedAt = new Date().toISOString();
234
+ } else {
235
+ instance.status = 'completed';
236
+ instance.currentStep = -1;
237
+ instance.history.push({ event: 'workflow_completed', at: new Date().toISOString() });
238
+ }
239
+ } else {
240
+ nextStep.status = 'awaiting_approval';
241
+ nextStep.startedAt = new Date().toISOString();
242
+ instance.currentStep = nextIdx;
243
+ instance.status = 'awaiting_approval';
244
+ instance.history.push({ event: 'awaiting_approval', step: nextIdx, at: new Date().toISOString() });
245
+ }
246
+ } else {
247
+ instance.currentStep = nextIdx;
248
+ nextStep.status = 'active';
249
+ nextStep.startedAt = new Date().toISOString();
250
+ }
251
+ }
252
+ instance.updatedAt = new Date().toISOString();
253
+ writeFileSync(pp, JSON.stringify(state, null, 2));
254
+ return { ok: true, instance };
255
+ }
256
+
257
+ export function approveWorkflowStep(tenantId, instanceId, approved = true) {
258
+ const pp = workflowsPath(tenantId);
259
+ if (!existsSync(pp)) return { ok: false, error: 'No workflows configured' };
260
+ let state = JSON.parse(readFileSync(pp, 'utf-8'));
261
+ const instance = (state.instances || []).find(i => i.instanceId === instanceId);
262
+ if (!instance) return { ok: false, error: `Instance ${instanceId} not found` };
263
+ if (instance.status !== 'awaiting_approval') return { ok: false, error: 'Workflow is not awaiting approval' };
264
+ const current = instance.steps[instance.currentStep];
265
+ if (!current) return { ok: false, error: 'No current step' };
266
+ if (approved) {
267
+ current.status = 'completed';
268
+ current.completedAt = new Date().toISOString();
269
+ current.result = { approved: true, approvedAt: new Date().toISOString() };
270
+ instance.history.push({ event: 'approved', step: instance.currentStep, at: new Date().toISOString() });
271
+ instance.status = 'running';
272
+ const nextIdx = instance.currentStep + 1;
273
+ if (nextIdx < instance.steps.length) {
274
+ instance.currentStep = nextIdx;
275
+ instance.steps[nextIdx].status = 'active';
276
+ instance.steps[nextIdx].startedAt = new Date().toISOString();
277
+ } else {
278
+ instance.status = 'completed';
279
+ instance.currentStep = -1;
280
+ instance.history.push({ event: 'workflow_completed', at: new Date().toISOString() });
281
+ }
282
+ } else {
283
+ current.status = 'rejected';
284
+ current.completedAt = new Date().toISOString();
285
+ current.result = { approved: false, rejectedAt: new Date().toISOString() };
286
+ instance.status = 'rejected';
287
+ instance.history.push({ event: 'rejected', step: instance.currentStep, at: new Date().toISOString() });
288
+ }
289
+ instance.updatedAt = new Date().toISOString();
290
+ writeFileSync(pp, JSON.stringify(state, null, 2));
291
+ return { ok: true, instance };
292
+ }
293
+
294
+ export function getWorkflowStatus(tenantId) {
295
+ const pp = workflowsPath(tenantId);
296
+ if (!existsSync(pp)) return { tenantId, configured: [], instances: [], summary: null };
297
+ const state = JSON.parse(readFileSync(pp, 'utf-8'));
298
+ const instances = state.instances || [];
299
+ const summary = {
300
+ total: instances.length,
301
+ running: instances.filter(i => i.status === 'running').length,
302
+ completed: instances.filter(i => i.status === 'completed').length,
303
+ awaitingApproval: instances.filter(i => i.status === 'awaiting_approval').length,
304
+ rejected: instances.filter(i => i.status === 'rejected').length,
305
+ };
306
+ return { tenantId, configured: state.configured || [], instances: instances.map(i => ({
307
+ instanceId: i.instanceId,
308
+ workflowId: i.workflowId,
309
+ status: i.status,
310
+ currentStep: i.currentStep >= 0 ? i.steps[i.currentStep]?.name : null,
311
+ progress: `${i.steps.filter(s => s.status === 'completed').length}/${i.steps.length}`,
312
+ createdAt: i.createdAt,
313
+ updatedAt: i.updatedAt,
314
+ })), summary };
315
+ }
316
+
317
+ export function getWorkflowInstance(tenantId, instanceId) {
318
+ const pp = workflowsPath(tenantId);
319
+ if (!existsSync(pp)) return null;
320
+ const state = JSON.parse(readFileSync(pp, 'utf-8'));
321
+ return (state.instances || []).find(i => i.instanceId === instanceId) || null;
322
+ }
@@ -32,6 +32,7 @@ const RUNTIME_DISCIPLINE_DST = resolve(RUNTIME_DST, 'discipline');
32
32
  const CONNECTOR_HOOKS_SRC = resolve(PKG_ROOT, 'hooks');
33
33
  const OPENCODE_PLUGINS_SRC = resolve(PKG_ROOT, 'opencode-plugins');
34
34
  const CLIENT_ONBOARDING_SRC = resolve(PKG_ROOT, 'CLIENT-ONBOARDING.md');
35
+ const DOCTRINE_TRIGGER_MAP_SRC = resolve(CONNECTOR_HOOKS_SRC, 'doctrine_trigger_map.json');
35
36
  const REQUIRED_HOOK_FILES = [
36
37
  'aria-harness-via-sdk.mjs',
37
38
  'aria-pre-tool-gate.mjs',
@@ -287,6 +288,10 @@ if (existsSync(SDK_SKILLS_SRC)) {
287
288
  if (existsSync(COGNITIVE_SKILLS_SRC)) {
288
289
  copyTree(COGNITIVE_SKILLS_SRC, join(RUNTIME_DISCIPLINE_DST, 'skills', 'aria-cognition'));
289
290
  }
291
+ if (existsSync(DOCTRINE_TRIGGER_MAP_SRC)) {
292
+ copyFileSync(DOCTRINE_TRIGGER_MAP_SRC, join(RUNTIME_DISCIPLINE_DST, 'doctrine_trigger_map.json'));
293
+ copyFileSync(DOCTRINE_TRIGGER_MAP_SRC, join(RUNTIME_DST, 'doctrine_trigger_map.json'));
294
+ }
290
295
  if (existsSync(CONNECTOR_HOOKS_SRC)) {
291
296
  copyTree(CONNECTOR_HOOKS_SRC, join(DIST_ASSETS_DST, 'hooks'));
292
297
  }
@@ -1,8 +1,11 @@
1
- import { existsSync, readFileSync, writeFileSync, copyFileSync, chmodSync, mkdirSync, readdirSync, statSync } from 'fs';
1
+ import { existsSync, readFileSync, writeFileSync, copyFileSync, chmodSync, mkdirSync, readdirSync, statSync, lstatSync, realpathSync } from 'fs';
2
2
  import { homedir } from 'os';
3
3
  import * as path from 'path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import type { AriaConfig } from '../config.js';
6
+ import { syncDoctrineTriggerMap } from './doctrine-trigger-map.js';
7
+ import { buildMustReadGuide } from './must-read.js';
8
+ import { connectShell } from './shell.js';
6
9
 
7
10
  // ── Hooks shipped with this package ──────────────────────────────────
8
11
  // The connector installs these into ~/.claude/hooks/ so Claude Code's
@@ -241,16 +244,27 @@ const HOOKS_BLOCK = {
241
244
  }],
242
245
  };
243
246
 
244
- function installHooks(claudeDir: string, logs: string[], opts: { force?: boolean } = {}): void {
245
- const hooksTargetDir = path.join(claudeDir, 'hooks');
246
- if (!existsSync(hooksTargetDir)) {
247
- mkdirSync(hooksTargetDir, { recursive: true, mode: 0o700 });
247
+ function discoverManagedHookSymlinkTargets(hooksTargetDir: string, logs: string[]): Map<string, string> {
248
+ const targets = new Map<string, string>();
249
+ for (const name of HOOK_FILES) {
250
+ const hookPath = path.join(hooksTargetDir, name);
251
+ if (!existsSync(hookPath)) continue;
252
+ try {
253
+ const stat = lstatSync(hookPath);
254
+ if (!stat.isSymbolicLink()) continue;
255
+ const targetPath = realpathSync(hookPath);
256
+ targets.set(name, targetPath);
257
+ logs.push(`Detected managed hook symlink: ${name} -> ${targetPath}`);
258
+ } catch {}
248
259
  }
249
- const sourceDir = packageHooksDir();
260
+ return targets;
261
+ }
262
+
263
+ function installHookHelpersToRoot(sourceDir: string, targetRoot: string, logs: string[]): void {
250
264
  for (const dirName of HOOK_DIRS) {
251
265
  const srcDir = path.join(sourceDir, dirName);
252
266
  if (!existsSync(srcDir)) continue;
253
- const dstDir = path.join(hooksTargetDir, dirName);
267
+ const dstDir = path.join(targetRoot, dirName);
254
268
  if (!existsSync(dstDir)) {
255
269
  mkdirSync(dstDir, { recursive: true, mode: 0o700 });
256
270
  }
@@ -263,42 +277,74 @@ function installHooks(claudeDir: string, logs: string[], opts: { force?: boolean
263
277
  if (child.endsWith('.mjs') || child.endsWith('.js')) {
264
278
  try { chmodSync(dst, 0o755); } catch {}
265
279
  }
266
- logs.push(`Installed hook helper: ${dirName}/${child}`);
280
+ logs.push(`Installed hook helper: ${path.relative(targetRoot, dst)}`);
267
281
  }
268
282
  }
283
+ }
284
+
285
+ function installHookFilesToRoot(
286
+ sourceDir: string,
287
+ targetRoot: string,
288
+ logs: string[],
289
+ opts: { force?: boolean },
290
+ symlinkTargets: Map<string, string>,
291
+ preserveSymlinkStubs: boolean,
292
+ ): void {
269
293
  for (const name of HOOK_FILES) {
270
294
  const src = path.join(sourceDir, name);
271
295
  if (!existsSync(src)) {
272
296
  logs.push(`⚠ hook source missing: ${src} (package may be incomplete)`);
273
297
  continue;
274
298
  }
275
- const dst = path.join(hooksTargetDir, name);
276
- // Idempotent skip — if existing file matches bundled file byte-for-byte,
277
- // no overwrite + no backup churn. Re-running connect stays clean.
299
+ const dst = path.join(targetRoot, name);
300
+ if (preserveSymlinkStubs && symlinkTargets.has(name)) {
301
+ logs.push(`Preserved managed hook symlink: ${name}`);
302
+ continue;
303
+ }
278
304
  if (existsSync(dst) && !isSamePath(src, dst) && !opts.force) {
279
305
  try {
280
306
  const existingContent = readFileSync(dst, 'utf-8');
281
307
  const newContent = readFileSync(src, 'utf-8');
282
308
  if (existingContent === newContent) {
283
- logs.push(`Hook unchanged: ${name}`);
309
+ logs.push(`Hook unchanged: ${path.relative(targetRoot, dst)}`);
284
310
  continue;
285
311
  }
286
312
  } catch {/* fall through to backup+overwrite */}
287
313
  }
288
- // Back up any existing real file before overwriting (never silently
289
- // clobber a user's customizations).
290
314
  if (existsSync(dst) && !isSamePath(src, dst)) {
291
315
  const backup = `${dst}.pre-aria-connect.${Date.now()}`;
292
- try { copyFileSync(dst, backup); logs.push(`Backed up existing ${name} → ${path.basename(backup)}`); } catch {}
316
+ try { copyFileSync(dst, backup); logs.push(`Backed up existing ${path.basename(dst)} → ${path.basename(backup)}`); } catch {}
293
317
  }
294
318
  copyFileSync(src, dst);
295
- // Only the .mjs scripts need executable perms; data files (e.g. the
296
- // doctrine_trigger_map.json shipped alongside hooks) get default 0o644.
297
319
  if (name.endsWith('.mjs') || name.endsWith('.js')) {
298
320
  try { chmodSync(dst, 0o755); } catch {}
299
321
  }
300
- logs.push(`Installed hook: ${name}`);
322
+ logs.push(`Installed hook: ${path.relative(targetRoot, dst)}`);
323
+ }
324
+ }
325
+
326
+ function installHooks(claudeDir: string, logs: string[], opts: { force?: boolean } = {}): void {
327
+ const hooksTargetDir = path.join(claudeDir, 'hooks');
328
+ if (!existsSync(hooksTargetDir)) {
329
+ mkdirSync(hooksTargetDir, { recursive: true, mode: 0o700 });
330
+ }
331
+ const sourceDir = packageHooksDir();
332
+ const symlinkTargets = discoverManagedHookSymlinkTargets(hooksTargetDir, logs);
333
+ const targetRoots = new Set<string>([hooksTargetDir]);
334
+ for (const targetPath of symlinkTargets.values()) {
335
+ targetRoots.add(path.dirname(targetPath));
336
+ }
337
+ for (const root of targetRoots) {
338
+ if (!existsSync(root)) {
339
+ mkdirSync(root, { recursive: true, mode: 0o700 });
340
+ }
341
+ installHookHelpersToRoot(sourceDir, root, logs);
342
+ installHookFilesToRoot(sourceDir, root, logs, opts, symlinkTargets, root === hooksTargetDir);
301
343
  }
344
+
345
+ const mustReadPath = path.join(claudeDir, 'ARIA_MUST_READ.md');
346
+ writeFileSync(mustReadPath, buildMustReadGuide('claude'), { mode: 0o644 });
347
+ logs.push(`Installed Claude must-read guide → ${mustReadPath}`);
302
348
  }
303
349
 
304
350
  // Install bundled SDK to ~/.claude/aria-sdk/. Hooks dynamic-import from this
@@ -389,6 +435,8 @@ Before any tool call, Read the harness packet at: $ARIA_HARNESS_PACKET_PATH
389
435
  (path is also in your environment as ARIA_HARNESS_PACKET_PATH variable)
390
436
  Cite it in your first cognition block — reference at least one axiom + one frame + one memory class from the packet.
391
437
  Without packet-citation in your first cognition block, your output will be rejected by aria-pre-tool-gate.mjs in your sub-process.
438
+
439
+ If you are a spawned sub-agent, also read the active Aria handoff file when present. Use its cognitionOperatingMethod to execute the task: substrate first, constraints second, action shape third, evidence return last. Your final response to the parent must include substrate used, decision delta, artifact impact, evidence, and unresolved risk.
392
440
  [/ARIA_HARNESS_BINDING]
393
441
 
394
442
  [ARIA_DISCOVERY_RECORDING — when you find a defect, doctrine violation, or notable observation during work]
@@ -397,6 +445,57 @@ Record findings to your session ledger via:
397
445
  This makes findings visible to the parent session's ledger-merge hook + the auto-fix spawner.
398
446
  [/ARIA_DISCOVERY_RECORDING]`;
399
447
 
448
+ const ARIA_GATE_PHASE_DIRECTIVE = `[ARIA_GATE_SHAPE — DO THIS EVERY TURN]
449
+ PRE-GATE before the first non-trivial tool call or non-trivial answer:
450
+ 1. Read the harness packet and any [ARIA_DIRECTION] / [ARIA_BINDING_PLAN] context already injected this turn.
451
+ 2. Choose the action shape before acting: tool call, text-only answer, or replan.
452
+ 3. Emit a visible <cognition> block using the required labels for this surface. Each lens must cite loaded substrate anchors and change the action, not merely describe values.
453
+ 4. Emit <applied_cognition> for every non-trivial answer or tool action so the model proves what cognition changed.
454
+ 5. If the action is destructive, deployment-related, or materially mutates state, emit a <verify> block before the tool call.
455
+ 6. If the action claims or changes an outcome, emit an <expected> block with a measurable predicate before the tool call or final answer.
456
+
457
+ MID-GATE when a hook blocks or warns:
458
+ 1. Do not repeat the same tool call or same draft unchanged.
459
+ 2. Name the exact missing requirement in one line.
460
+ 3. Re-emit the missing structure only: <cognition>, <verify>, <expected>, and/or <reflection>.
461
+ 4. Change the plan before retrying. If recovering from a repeated block, do not put the retrying tool call in the same message as the diagnosis.
462
+ 5. If the blocker is stale gate residue or stale ledger state, say that explicitly. Never invent proofOfFix or fake task resolution.
463
+
464
+ LOOP-RECOVERY after the second block of the same shape:
465
+ 1. Emit [LOOP_RECOVERY gate=<name> cause=<one line>] in plain text.
466
+ 2. Summarize what repeated, what changed, and why the next attempt is materially different.
467
+ 3. Produce the corrected preflight structure first. Retry only after the structure is complete.
468
+
469
+ OUTPUT-GATE before final emission:
470
+ 1. Keep user-visible cognition readable, but preserve the canonical lens labels. Readability comes from the prose inside each lens, never from replacing the lens names.
471
+ 2. If Mizan warns, include <reflection> addressing the exact warning instead of repeating lens text.
472
+ 3. Discoveries are atomic with their fixes. Either fix inline now or bind to a real tracked task with proof-shaped evidence.
473
+ [/ARIA_GATE_SHAPE]`;
474
+
475
+ const ARIA_STRUCTURAL_COGNITION_CONTRACT = `[ARIA_STRUCTURAL_COGNITION_CONTRACT — NON-NEGOTIABLE]
476
+ The harness is not a style guide. It is a runtime contract.
477
+
478
+ Aria cognition is a work method, not a footer. Use it to improve the input and output before gates fire:
479
+ 1. Perceive the real substrate: user intent, repo/runtime state, loaded harness packet, memories, prior tool results, and uncertainty.
480
+ 2. Infer constraints: what must be true, what must not be harmed, what prior doctrine changes, and what evidence is missing.
481
+ 3. Shape the next action: choose the smallest useful tool call, edit, question, or answer that satisfies those constraints.
482
+ 4. Improve the artifact: make the tool input, code change, review finding, or prose more specific because of the cognition.
483
+ 5. Predict the observable result: define what would prove the action or answer succeeded before claiming it.
484
+ 6. Report evidence: final output should distinguish observed fact, bounded inference, unresolved risk, and next real action.
485
+
486
+ For every non-trivial response or action:
487
+ 1. <cognition> must use the canonical lens labels for the active tier and each lens must contain real substrate anchors: axiom:, frame:, memory:, doctrine:, packet:, or active language:.
488
+ 2. The cognition must reference first_principle explicitly and only cite substrate that was loaded this turn.
489
+ 3. Each lens must affect the action: if a lens reveals risk, missing evidence, or a better path, change the tool input/output accordingly.
490
+ 4. <applied_cognition> must include all fields exactly: decision_delta, dominant_domain, binds_to, expected_predicate, artifact_change.
491
+ 5. decision_delta must name what changed because cognition ran. Values like none, unchanged, no change, or n/a are rejected.
492
+ 6. binds_to must name the exact answer, tool call, file mutation, deploy, review, or decision being shaped.
493
+ 7. expected_predicate must be observable: numeric, boolean, state string, command result, endpoint result, or explicit unverified boundary.
494
+ 8. artifact_change must describe the semantic effect on the artifact or output, not restate the task.
495
+ 9. If a gate blocks, do not apologize-loop. Read the block reason, re-open the must-read guide, and re-author the missing structure before retrying.
496
+ 10. Before long or risky final prose, dry-run the draft with the Claude pre-emit validator when available.
497
+ [/ARIA_STRUCTURAL_COGNITION_CONTRACT]`;
498
+
400
499
  function buildAriaSystemBlock(config: AriaConfig): string {
401
500
  const repoList = config.repositories.map((r) => `- ${r.name} (${r.path})`).join('\n');
402
501
  const schemaText = Object.entries(config.schemaImages)
@@ -410,6 +509,8 @@ You are augmented with Aria's cognitive harness. This provides:
410
509
  - 8-lens cognition: multi-perspective analysis for every decision
411
510
 
412
511
  ${ARIA_HARNESS_BINDING_PREFIX}
512
+ ${ARIA_GATE_PHASE_DIRECTIVE}
513
+ ${ARIA_STRUCTURAL_COGNITION_CONTRACT}
413
514
 
414
515
  [SELF-GATE PROTOCOL]
415
516
  Before emitting any claim, verify against these hard constraints:
@@ -463,8 +564,10 @@ export async function connectClaudeCode(
463
564
  ? settings.systemPromptPrefix
464
565
  : '';
465
566
 
466
- if (existing.includes('ARIA HARNESS')) {
467
- logs.push('Aria harness prefix already present — skipping prompt injection');
567
+ const ariaBlockRx = /<!-- ARIA HARNESS.*?<!-- END ARIA HARNESS -->/s;
568
+ if (ariaBlockRx.test(existing)) {
569
+ settings.systemPromptPrefix = existing.replace(ariaBlockRx, ariaBlock);
570
+ logs.push('Refreshed Aria harness systemPromptPrefix block');
468
571
  } else {
469
572
  settings.systemPromptPrefix = existing
470
573
  ? `${ariaBlock}\n\n${existing}`
@@ -477,6 +580,7 @@ export async function connectClaudeCode(
477
580
  // just a system-prompt addendum.
478
581
  installHooks(claudeDir, logs, { force: opts.force });
479
582
  installSdk(claudeDir, logs);
583
+ syncDoctrineTriggerMap(logs);
480
584
  wireHooksBlock(settings, logs);
481
585
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', { mode: 0o600 });
482
586
 
@@ -498,6 +602,8 @@ export async function connectClaudeCode(
498
602
  }
499
603
  }
500
604
 
605
+ logs.push(...await connectShell('claude', config));
606
+
501
607
  return logs;
502
608
  }
503
609