delimit-cli 4.0.3 → 4.0.4
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.
- package/README.md +9 -242
- package/bin/delimit-cli.js +352 -4
- package/bin/delimit-setup.js +30 -2
- package/gateway/ai/agent_dispatch.py +34 -2
- package/gateway/ai/github_scanner.py +1 -1
- package/gateway/ai/ledger_manager.py +13 -3
- package/gateway/ai/ledger_propose.py +240 -0
- package/gateway/ai/loop_engine.py +175 -372
- package/gateway/ai/notify.py +700 -13
- package/gateway/ai/reddit_proxy.py +106 -0
- package/gateway/ai/reddit_scanner.py +34 -0
- package/gateway/ai/server.py +343 -81
- package/gateway/ai/siem_streaming.py +290 -0
- package/gateway/ai/social_daemon.py +189 -0
- package/gateway/ai/swarm.py +434 -0
- package/lib/continuity-resolver.js +325 -0
- package/lib/cross-model-hooks.js +212 -0
- package/lib/delimit-template.js +5 -0
- package/lib/session-shell.js +655 -0
- package/lib/session-worker.js +479 -0
- package/package.json +1 -1
- package/scripts/security-check.sh +12 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
const { resolveContinuityContext } = require('./continuity-resolver');
|
|
5
|
+
|
|
6
|
+
function readJson(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function writeJson(filePath, payload) {
|
|
15
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
16
|
+
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2) + '\n');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readJsonl(filePath) {
|
|
20
|
+
if (!fs.existsSync(filePath)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
return fs.readFileSync(filePath, 'utf-8')
|
|
24
|
+
.split('\n')
|
|
25
|
+
.map(line => line.trim())
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.map(line => {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(line);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildLedgerSnapshot(ledgerPath) {
|
|
38
|
+
const entries = readJsonl(ledgerPath);
|
|
39
|
+
const latestById = new Map();
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (!entry || !entry.id) continue;
|
|
42
|
+
const current = latestById.get(entry.id) || {};
|
|
43
|
+
if (entry.type === 'update') {
|
|
44
|
+
latestById.set(entry.id, {
|
|
45
|
+
...current,
|
|
46
|
+
...entry,
|
|
47
|
+
id: current.id || entry.id,
|
|
48
|
+
title: current.title || entry.title,
|
|
49
|
+
description: current.description || entry.description,
|
|
50
|
+
priority: current.priority || entry.priority,
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
latestById.set(entry.id, {
|
|
54
|
+
...entry,
|
|
55
|
+
...current,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const priorityWeight = { P0: 0, P1: 1, P2: 2 };
|
|
60
|
+
const open = Array.from(latestById.values())
|
|
61
|
+
.filter(item => !['done', 'blocked'].includes(String(item.status || 'open')))
|
|
62
|
+
.sort((a, b) => (priorityWeight[a.priority] ?? 9) - (priorityWeight[b.priority] ?? 9));
|
|
63
|
+
return {
|
|
64
|
+
openCount: open.length,
|
|
65
|
+
nextItem: open[0] || null,
|
|
66
|
+
openItems: open,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function writeWorkerState(statePath, payload) {
|
|
71
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
72
|
+
fs.writeFileSync(statePath, JSON.stringify(payload, null, 2) + '\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeActivePointer(pointerPath, statePath) {
|
|
76
|
+
fs.mkdirSync(path.dirname(pointerPath), { recursive: true });
|
|
77
|
+
fs.writeFileSync(pointerPath, JSON.stringify({ statePath, updatedAt: new Date().toISOString() }, null, 2) + '\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function appendJsonl(filePath, payload) {
|
|
81
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
82
|
+
fs.appendFileSync(filePath, JSON.stringify(payload) + '\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function selectCurrentItem(executionState, ledger) {
|
|
86
|
+
const currentId = executionState?.currentItem?.id;
|
|
87
|
+
if (currentId) {
|
|
88
|
+
const stillOpen = ledger.openItems.find(item => item.id === currentId);
|
|
89
|
+
if (stillOpen) {
|
|
90
|
+
return {
|
|
91
|
+
id: stillOpen.id,
|
|
92
|
+
title: stillOpen.title || '',
|
|
93
|
+
priority: stillOpen.priority || '',
|
|
94
|
+
status: stillOpen.status || 'open',
|
|
95
|
+
description: stillOpen.description || '',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!ledger.nextItem) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
id: ledger.nextItem.id,
|
|
104
|
+
title: ledger.nextItem.title || '',
|
|
105
|
+
priority: ledger.nextItem.priority || '',
|
|
106
|
+
status: ledger.nextItem.status || 'open',
|
|
107
|
+
description: ledger.nextItem.description || '',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function loadExecutionState(executionPath) {
|
|
112
|
+
return readJson(executionPath) || {};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function classifyItem(item) {
|
|
116
|
+
const text = `${item?.title || ''} ${item?.description || ''}`.toLowerCase();
|
|
117
|
+
if (/deploy|release|publish|production/.test(text)) return 'deploy';
|
|
118
|
+
if (/ui|ux|dashboard|design|mobile/.test(text)) return 'product';
|
|
119
|
+
if (/test|lint|ci|smoke|coverage/.test(text)) return 'verification';
|
|
120
|
+
if (/docs|readme|guide|copy|content/.test(text)) return 'docs';
|
|
121
|
+
if (/social|outreach|reddit|x |twitter|github issue|pr/.test(text)) return 'growth';
|
|
122
|
+
return 'implementation';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildTaskBrief(context, currentItem, bootstrapState, phase) {
|
|
126
|
+
if (!currentItem) {
|
|
127
|
+
return {
|
|
128
|
+
generatedAt: new Date().toISOString(),
|
|
129
|
+
venture: context.venture,
|
|
130
|
+
repo: context.repoRoot || process.cwd(),
|
|
131
|
+
phase,
|
|
132
|
+
summary: 'No active ledger item selected.',
|
|
133
|
+
recommendedAction: 'Wait for a new open item or switch ventures.',
|
|
134
|
+
guardrails: [
|
|
135
|
+
'Do not mutate ledgers outside the current venture.',
|
|
136
|
+
'Keep governance active before any deploy or publish action.',
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const category = classifyItem(currentItem);
|
|
141
|
+
const recommendedActionByCategory = {
|
|
142
|
+
deploy: 'Inspect repo state, tests, and governance before attempting any release step.',
|
|
143
|
+
product: 'Inspect the relevant UI surface, validate responsiveness, and make the next controlled change.',
|
|
144
|
+
verification: 'Run the smallest meaningful verification step first, then update the item with results.',
|
|
145
|
+
docs: 'Open the relevant docs surface and prepare the next user-visible improvement.',
|
|
146
|
+
growth: 'Inspect the target surface and prepare the next governed draft or outreach action.',
|
|
147
|
+
implementation: 'Inspect the code area tied to this item and prepare the next governed execution step.',
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
generatedAt: new Date().toISOString(),
|
|
151
|
+
venture: context.venture,
|
|
152
|
+
repo: context.repoRoot || process.cwd(),
|
|
153
|
+
phase,
|
|
154
|
+
item: {
|
|
155
|
+
id: currentItem.id,
|
|
156
|
+
title: currentItem.title || '',
|
|
157
|
+
priority: currentItem.priority || '',
|
|
158
|
+
status: currentItem.status || 'open',
|
|
159
|
+
description: currentItem.description || '',
|
|
160
|
+
},
|
|
161
|
+
category,
|
|
162
|
+
summary: `${currentItem.id} ${currentItem.title || '(untitled)'}`.trim(),
|
|
163
|
+
recommendedAction: recommendedActionByCategory[category] || recommendedActionByCategory.implementation,
|
|
164
|
+
guardrails: [
|
|
165
|
+
'Stay within the current venture and repo unless the ledger item explicitly requires cross-venture work.',
|
|
166
|
+
'Do not deploy, publish, or use secrets without an explicit governed gate.',
|
|
167
|
+
'Write outcomes back to ledger, session state, and memory before moving on.',
|
|
168
|
+
],
|
|
169
|
+
latestSession: bootstrapState?.latestSession?.summary || null,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function identifyOwnerActions(taskBrief, executionPlan) {
|
|
174
|
+
if (!taskBrief?.item) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
const actions = [];
|
|
178
|
+
const category = taskBrief.category || 'implementation';
|
|
179
|
+
if (['deploy', 'growth'].includes(category)) {
|
|
180
|
+
actions.push({
|
|
181
|
+
id: `${taskBrief.item.id}:owner-escalation`,
|
|
182
|
+
itemId: taskBrief.item.id,
|
|
183
|
+
title: `${taskBrief.item.id} requires owner review`,
|
|
184
|
+
summary: taskBrief.recommendedAction,
|
|
185
|
+
channels: ['dashboard', 'email', 'telegram'],
|
|
186
|
+
status: 'open',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if ((executionPlan?.repoSnapshot?.gitStatus || []).length > 0) {
|
|
190
|
+
actions.push({
|
|
191
|
+
id: `${taskBrief.item.id}:dirty-repo`,
|
|
192
|
+
itemId: taskBrief.item.id,
|
|
193
|
+
title: `${taskBrief.item.id} has existing dirty files in repo context`,
|
|
194
|
+
summary: 'Proceed without blocking, but surface the dirty repo state to the owner and dashboard.',
|
|
195
|
+
channels: ['dashboard', 'email', 'telegram'],
|
|
196
|
+
status: 'open',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return actions;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function safeExecFile(command, args, cwd) {
|
|
203
|
+
try {
|
|
204
|
+
return execFileSync(command, args, {
|
|
205
|
+
cwd,
|
|
206
|
+
encoding: 'utf-8',
|
|
207
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
208
|
+
timeout: 3000,
|
|
209
|
+
}).trim();
|
|
210
|
+
} catch {
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function collectRepoSnapshot(context) {
|
|
216
|
+
const repoRoot = context.repoRoot || process.cwd();
|
|
217
|
+
const gitStatus = safeExecFile('git', ['status', '--short'], repoRoot)
|
|
218
|
+
.split('\n')
|
|
219
|
+
.map(line => line.trim())
|
|
220
|
+
.filter(Boolean)
|
|
221
|
+
.slice(0, 20);
|
|
222
|
+
const topLevelEntries = fs.existsSync(repoRoot)
|
|
223
|
+
? fs.readdirSync(repoRoot, { withFileTypes: true })
|
|
224
|
+
.filter((entry) => !entry.name.startsWith('.'))
|
|
225
|
+
.slice(0, 30)
|
|
226
|
+
.map((entry) => ({
|
|
227
|
+
name: entry.name,
|
|
228
|
+
type: entry.isDirectory() ? 'dir' : 'file',
|
|
229
|
+
}))
|
|
230
|
+
: [];
|
|
231
|
+
const hasPackageJson = fs.existsSync(path.join(repoRoot, 'package.json'));
|
|
232
|
+
const hasPyproject = fs.existsSync(path.join(repoRoot, 'pyproject.toml'));
|
|
233
|
+
const hasCargo = fs.existsSync(path.join(repoRoot, 'Cargo.toml'));
|
|
234
|
+
return {
|
|
235
|
+
repoRoot,
|
|
236
|
+
capturedAt: new Date().toISOString(),
|
|
237
|
+
gitStatus,
|
|
238
|
+
topLevelEntries,
|
|
239
|
+
toolchain: {
|
|
240
|
+
node: hasPackageJson,
|
|
241
|
+
python: hasPyproject,
|
|
242
|
+
rust: hasCargo,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function scoreEntryMatch(entryName, currentItem) {
|
|
248
|
+
const haystack = `${currentItem?.title || ''} ${currentItem?.description || ''}`.toLowerCase();
|
|
249
|
+
const tokens = entryName.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
250
|
+
return tokens.reduce((score, token) => (
|
|
251
|
+
token.length >= 3 && haystack.includes(token) ? score + 1 : score
|
|
252
|
+
), 0);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function inferTargetAreas(currentItem, repoSnapshot) {
|
|
256
|
+
const ranked = (repoSnapshot.topLevelEntries || [])
|
|
257
|
+
.map((entry) => ({
|
|
258
|
+
...entry,
|
|
259
|
+
score: scoreEntryMatch(entry.name, currentItem),
|
|
260
|
+
}))
|
|
261
|
+
.filter((entry) => entry.score > 0)
|
|
262
|
+
.sort((a, b) => b.score - a.score)
|
|
263
|
+
.slice(0, 5)
|
|
264
|
+
.map((entry) => entry.name);
|
|
265
|
+
if (ranked.length > 0) {
|
|
266
|
+
return ranked;
|
|
267
|
+
}
|
|
268
|
+
const fallbacks = [];
|
|
269
|
+
if (repoSnapshot.toolchain.node) fallbacks.push('package.json');
|
|
270
|
+
if (repoSnapshot.toolchain.python) fallbacks.push('pyproject.toml');
|
|
271
|
+
const commonDirs = repoSnapshot.topLevelEntries
|
|
272
|
+
.filter((entry) => entry.type === 'dir' && ['app', 'src', 'pages', 'components', 'lib', 'tests'].includes(entry.name))
|
|
273
|
+
.map((entry) => entry.name);
|
|
274
|
+
return [...fallbacks, ...commonDirs].slice(0, 5);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function buildPlanSteps(category, repoSnapshot, targetAreas) {
|
|
278
|
+
const base = [
|
|
279
|
+
'Review the current ledger item and repo context.',
|
|
280
|
+
'Inspect the highest-signal target areas before editing.',
|
|
281
|
+
];
|
|
282
|
+
const categorySteps = {
|
|
283
|
+
deploy: [
|
|
284
|
+
'Confirm governance, repo cleanliness, and verification status before any release action.',
|
|
285
|
+
'Prepare a release-safe checklist and stop for approval before deploy.',
|
|
286
|
+
],
|
|
287
|
+
product: [
|
|
288
|
+
'Inspect the relevant UI surface and responsive behavior first.',
|
|
289
|
+
'Make one controlled change, then validate visually or with the smallest available test.',
|
|
290
|
+
],
|
|
291
|
+
verification: [
|
|
292
|
+
'Run the smallest meaningful verification command first.',
|
|
293
|
+
'Capture the result and only then decide whether code changes are needed.',
|
|
294
|
+
],
|
|
295
|
+
docs: [
|
|
296
|
+
'Inspect the relevant content surface and supporting references.',
|
|
297
|
+
'Make the next user-visible improvement and verify wording/links.',
|
|
298
|
+
],
|
|
299
|
+
growth: [
|
|
300
|
+
'Inspect the target content or thread before drafting.',
|
|
301
|
+
'Prepare the next governed outreach action without posting automatically.',
|
|
302
|
+
],
|
|
303
|
+
implementation: [
|
|
304
|
+
'Inspect the relevant code path and adjacent tests.',
|
|
305
|
+
'Prepare the smallest viable implementation slice before editing.',
|
|
306
|
+
],
|
|
307
|
+
};
|
|
308
|
+
const tail = repoSnapshot.gitStatus.length > 0
|
|
309
|
+
? ['Account for existing dirty files before making changes.']
|
|
310
|
+
: ['Repo appears clean enough for a focused implementation slice.'];
|
|
311
|
+
if (targetAreas.length > 0) {
|
|
312
|
+
tail.unshift(`Start with: ${targetAreas.join(', ')}`);
|
|
313
|
+
}
|
|
314
|
+
return [...base, ...(categorySteps[category] || categorySteps.implementation), ...tail];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function buildExecutionPlan(context, currentItem, taskBrief, repoSnapshot, executionState) {
|
|
318
|
+
if (!currentItem) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
const targetAreas = inferTargetAreas(currentItem, repoSnapshot);
|
|
322
|
+
const planItemId = executionState?.plan?.itemId;
|
|
323
|
+
const reuseExisting = planItemId === currentItem.id && executionState?.plan?.steps?.length;
|
|
324
|
+
const category = taskBrief.category || classifyItem(currentItem);
|
|
325
|
+
const steps = reuseExisting
|
|
326
|
+
? executionState.plan.steps
|
|
327
|
+
: buildPlanSteps(category, repoSnapshot, targetAreas);
|
|
328
|
+
return {
|
|
329
|
+
generatedAt: new Date().toISOString(),
|
|
330
|
+
venture: context.venture,
|
|
331
|
+
repo: context.repoRoot || process.cwd(),
|
|
332
|
+
itemId: currentItem.id,
|
|
333
|
+
category,
|
|
334
|
+
phase: currentItem ? 'planned' : 'idle',
|
|
335
|
+
targetAreas,
|
|
336
|
+
repoSnapshot,
|
|
337
|
+
summary: taskBrief.summary,
|
|
338
|
+
recommendedAction: taskBrief.recommendedAction,
|
|
339
|
+
steps,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function createWorkerPayload(context, executionState, status = 'running') {
|
|
344
|
+
const ledgerPath = path.join(context.ledgerRoot, 'operations.jsonl');
|
|
345
|
+
const ledger = buildLedgerSnapshot(ledgerPath);
|
|
346
|
+
const bootstrapPath = path.join(context.continuityRoot, 'bootstrap-state.json');
|
|
347
|
+
const bootstrapState = readJson(bootstrapPath);
|
|
348
|
+
const currentItem = selectCurrentItem(executionState, ledger);
|
|
349
|
+
const phase = currentItem ? (executionState?.phase || 'ready') : 'idle';
|
|
350
|
+
return {
|
|
351
|
+
pid: process.pid,
|
|
352
|
+
status,
|
|
353
|
+
phase,
|
|
354
|
+
updatedAt: new Date().toISOString(),
|
|
355
|
+
startedAt: process.env.DELIMIT_WORKER_STARTED_AT || new Date().toISOString(),
|
|
356
|
+
actor: context.actor,
|
|
357
|
+
venture: context.venture,
|
|
358
|
+
repo: context.repoRoot || process.cwd(),
|
|
359
|
+
ledgerRoot: context.ledgerRoot,
|
|
360
|
+
ledgerScope: context.ledgerScope,
|
|
361
|
+
continuityRoot: context.continuityRoot,
|
|
362
|
+
bootstrapPath,
|
|
363
|
+
bootstrapState,
|
|
364
|
+
openItemCount: ledger.openCount,
|
|
365
|
+
currentItem,
|
|
366
|
+
nextItem: ledger.nextItem ? {
|
|
367
|
+
id: ledger.nextItem.id,
|
|
368
|
+
title: ledger.nextItem.title || '',
|
|
369
|
+
priority: ledger.nextItem.priority || '',
|
|
370
|
+
status: ledger.nextItem.status || 'open',
|
|
371
|
+
} : null,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function runWorkerLoop(options = {}) {
|
|
376
|
+
const context = resolveContinuityContext({ cwd: options.cwd || process.cwd() });
|
|
377
|
+
const runId = process.env.DELIMIT_WORKER_RUN_ID || String(Date.now());
|
|
378
|
+
const statePath = path.join(context.continuityRoot, `worker-state-${runId}.json`);
|
|
379
|
+
const pointerPath = path.join(context.continuityRoot, 'active-worker.json');
|
|
380
|
+
const executionPath = path.join(context.continuityRoot, 'execution-state.json');
|
|
381
|
+
const startedAt = new Date().toISOString();
|
|
382
|
+
process.env.DELIMIT_WORKER_STARTED_AT = startedAt;
|
|
383
|
+
let lastSignature = '';
|
|
384
|
+
|
|
385
|
+
const update = (status = 'running') => {
|
|
386
|
+
const executionState = loadExecutionState(executionPath);
|
|
387
|
+
const payload = createWorkerPayload(context, executionState, status);
|
|
388
|
+
const taskBriefPath = path.join(context.continuityRoot, 'task-brief.json');
|
|
389
|
+
const taskBrief = buildTaskBrief(context, payload.currentItem, payload.bootstrapState, payload.phase);
|
|
390
|
+
const repoSnapshot = collectRepoSnapshot(context);
|
|
391
|
+
const executionPlanPath = path.join(context.continuityRoot, 'execution-plan.json');
|
|
392
|
+
const executionPlan = buildExecutionPlan(context, payload.currentItem, taskBrief, repoSnapshot, executionState);
|
|
393
|
+
const ownerActionsPath = path.join(context.continuityRoot, 'owner-actions.json');
|
|
394
|
+
const ownerActions = identifyOwnerActions(taskBrief, executionPlan);
|
|
395
|
+
const effectivePhase = payload.currentItem ? (executionPlan ? 'planned' : 'ready') : 'idle';
|
|
396
|
+
writeJson(executionPath, {
|
|
397
|
+
...executionState,
|
|
398
|
+
venture: context.venture,
|
|
399
|
+
repo: context.repoRoot || process.cwd(),
|
|
400
|
+
ledgerRoot: context.ledgerRoot,
|
|
401
|
+
phase: effectivePhase,
|
|
402
|
+
currentItem: payload.currentItem,
|
|
403
|
+
nextItem: payload.nextItem,
|
|
404
|
+
taskBriefPath,
|
|
405
|
+
executionPlanPath,
|
|
406
|
+
ownerActionsPath,
|
|
407
|
+
plan: executionPlan ? {
|
|
408
|
+
itemId: executionPlan.itemId,
|
|
409
|
+
category: executionPlan.category,
|
|
410
|
+
targetAreas: executionPlan.targetAreas,
|
|
411
|
+
steps: executionPlan.steps,
|
|
412
|
+
} : null,
|
|
413
|
+
ownerActions,
|
|
414
|
+
updatedAt: payload.updatedAt,
|
|
415
|
+
});
|
|
416
|
+
writeJson(taskBriefPath, taskBrief);
|
|
417
|
+
if (executionPlan) {
|
|
418
|
+
writeJson(executionPlanPath, executionPlan);
|
|
419
|
+
}
|
|
420
|
+
writeJson(ownerActionsPath, {
|
|
421
|
+
generatedAt: payload.updatedAt,
|
|
422
|
+
venture: context.venture,
|
|
423
|
+
repo: context.repoRoot || process.cwd(),
|
|
424
|
+
itemId: payload.currentItem?.id || null,
|
|
425
|
+
actions: ownerActions,
|
|
426
|
+
nonBlocking: true,
|
|
427
|
+
});
|
|
428
|
+
writeWorkerState(statePath, {
|
|
429
|
+
...payload,
|
|
430
|
+
phase: effectivePhase,
|
|
431
|
+
ownerActionCount: ownerActions.length,
|
|
432
|
+
});
|
|
433
|
+
writeActivePointer(pointerPath, statePath);
|
|
434
|
+
const signature = JSON.stringify({
|
|
435
|
+
phase: effectivePhase,
|
|
436
|
+
currentItem: payload.currentItem?.id || null,
|
|
437
|
+
nextItem: payload.nextItem?.id || null,
|
|
438
|
+
status,
|
|
439
|
+
});
|
|
440
|
+
if (signature !== lastSignature) {
|
|
441
|
+
appendJsonl(path.join(context.continuityRoot, 'execution-log.jsonl'), {
|
|
442
|
+
timestamp: payload.updatedAt,
|
|
443
|
+
venture: context.venture,
|
|
444
|
+
repo: context.repoRoot || process.cwd(),
|
|
445
|
+
phase: effectivePhase,
|
|
446
|
+
status,
|
|
447
|
+
currentItem: payload.currentItem ? {
|
|
448
|
+
id: payload.currentItem.id,
|
|
449
|
+
title: payload.currentItem.title || '',
|
|
450
|
+
} : null,
|
|
451
|
+
nextItem: payload.nextItem ? {
|
|
452
|
+
id: payload.nextItem.id,
|
|
453
|
+
title: payload.nextItem.title || '',
|
|
454
|
+
} : null,
|
|
455
|
+
});
|
|
456
|
+
lastSignature = signature;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
update('running');
|
|
461
|
+
const interval = setInterval(() => update('running'), 15000);
|
|
462
|
+
|
|
463
|
+
const shutdown = (status) => {
|
|
464
|
+
clearInterval(interval);
|
|
465
|
+
update(status);
|
|
466
|
+
process.exit(0);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
process.on('SIGTERM', () => shutdown('stopped'));
|
|
470
|
+
process.on('SIGINT', () => shutdown('stopped'));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (require.main === module) {
|
|
474
|
+
runWorkerLoop();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
module.exports = {
|
|
478
|
+
runWorkerLoop,
|
|
479
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delimit-cli",
|
|
3
3
|
"mcpName": "io.github.delimit-ai/delimit-mcp-server",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.4",
|
|
5
5
|
"description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -52,6 +52,18 @@ else
|
|
|
52
52
|
echo "✅ clean"
|
|
53
53
|
fi
|
|
54
54
|
|
|
55
|
+
# 5. Continuity state must never ship
|
|
56
|
+
echo -n " Continuity state... "
|
|
57
|
+
if find "$TMPDIR/package/" \( -path "*/.delimit/*" -o -path "*/continuity/*" -o -name "*.jsonl" -o -name "session_*.json" -o -name "handoff_*.json" \) | grep -v "package-lock.json" 2>/dev/null; then
|
|
58
|
+
echo "❌ CONTINUITY ARTIFACTS IN PACKAGE"
|
|
59
|
+
FAIL=1
|
|
60
|
+
elif grep -rEi "/root/\.delimit|/home/[^/]+/\.delimit|/Users/[^/]+/\.delimit|C:\\Users\\[^\]+\\.delimit" "$TMPDIR/package/" --include="*.js" --include="*.json" 2>/dev/null | grep -v "security-scan-ignore"; then
|
|
61
|
+
echo "❌ CONTINUITY PATHS LEAKED INTO PACKAGE"
|
|
62
|
+
FAIL=1
|
|
63
|
+
else
|
|
64
|
+
echo "✅ clean"
|
|
65
|
+
fi
|
|
66
|
+
|
|
55
67
|
# Cleanup
|
|
56
68
|
rm -rf "$TMPDIR"
|
|
57
69
|
|