job-forge 2.14.37 → 2.14.39
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/.codex/config.toml +1 -1
- package/.cursor/rules/main.mdc +1 -1
- package/.opencode/instructions.md +8 -0
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +5 -5
- package/batch/README.md +8 -7
- package/batch/batch-runner.sh +2 -2
- package/bin/create-job-forge.mjs +7 -4
- package/bin/sync.mjs +36 -2
- package/docs/ARCHITECTURE.md +27 -26
- package/docs/CUSTOMIZATION.md +25 -25
- package/docs/README.md +1 -1
- package/docs/SETUP.md +2 -2
- package/iso/instructions.md +1 -1
- package/iso/instructions.opencode.md +8 -0
- package/lib/jobforge-cache.mjs +1 -1
- package/lib/jobforge-canon.mjs +1 -1
- package/lib/jobforge-capabilities.mjs +1 -1
- package/lib/jobforge-context.mjs +1 -1
- package/lib/jobforge-contracts.mjs +1 -1
- package/lib/jobforge-facts.mjs +1 -1
- package/lib/jobforge-index.mjs +1 -1
- package/lib/jobforge-ledger.mjs +1 -1
- package/lib/jobforge-lineage.mjs +1 -1
- package/lib/jobforge-migrate.mjs +1 -1
- package/lib/jobforge-observability.mjs +847 -0
- package/lib/jobforge-postflight.mjs +1 -1
- package/lib/jobforge-preflight.mjs +1 -1
- package/lib/jobforge-prioritize.mjs +1 -1
- package/lib/jobforge-redact.mjs +1 -1
- package/lib/jobforge-score.mjs +1 -1
- package/lib/jobforge-timeline.mjs +1 -1
- package/models.yaml +1 -1
- package/modes/batch.md +1 -1
- package/modes/reference-setup.md +1 -1
- package/opencode.json +2 -1
- package/package.json +29 -26
- package/scripts/batch-orchestrator.mjs +160 -11
- package/scripts/cache.mjs +1 -1
- package/scripts/canon.mjs +1 -1
- package/scripts/check-helper-integration.mjs +19 -19
- package/scripts/check-iso-smoke.mjs +2 -0
- package/scripts/facts.mjs +1 -1
- package/scripts/guard.mjs +115 -191
- package/scripts/index.mjs +1 -1
- package/scripts/ledger.mjs +1 -1
- package/scripts/lineage.mjs +1 -1
- package/scripts/migrate.mjs +1 -1
- package/scripts/postflight.mjs +1 -1
- package/scripts/preflight.mjs +1 -1
- package/scripts/prioritize.mjs +1 -1
- package/scripts/redact.mjs +1 -1
- package/scripts/score.mjs +1 -1
- package/scripts/telemetry.mjs +214 -450
- package/scripts/timeline.mjs +1 -1
- package/scripts/trace.mjs +104 -233
- package/templates/portals.example.yml +1 -1
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
loadPostflightConfig,
|
|
5
5
|
parseJson,
|
|
6
6
|
settlePostflight,
|
|
7
|
-
} from '@
|
|
7
|
+
} from '@agent-pattern-labs/iso-postflight';
|
|
8
8
|
|
|
9
9
|
export const POSTFLIGHT_CONFIG_FILE = 'templates/postflight.json';
|
|
10
10
|
export const POSTFLIGHT_WORKFLOW = 'jobforge.apply';
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
prioritize,
|
|
8
8
|
selectPrioritized,
|
|
9
9
|
verifyPrioritizeResult,
|
|
10
|
-
} from '@
|
|
10
|
+
} from '@agent-pattern-labs/iso-prioritize';
|
|
11
11
|
import { ensureJobForgeFacts } from './jobforge-facts.mjs';
|
|
12
12
|
import { dueJobForgeTimeline } from './jobforge-timeline.mjs';
|
|
13
13
|
|
package/lib/jobforge-redact.mjs
CHANGED
package/lib/jobforge-score.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
parseJsonLines,
|
|
9
9
|
planTimeline,
|
|
10
10
|
verifyTimelineResult,
|
|
11
|
-
} from '@
|
|
11
|
+
} from '@agent-pattern-labs/iso-timeline';
|
|
12
12
|
import { DATA_APPS_DIR, PROJECT_DIR, readAllEntries } from '../tracker-lib.mjs';
|
|
13
13
|
import { jobForgeCompanyRoleKey, jobForgeUrlKey, legacyCompanyRoleKey, legacyUrlKey } from './jobforge-canon.mjs';
|
|
14
14
|
|
package/models.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# JobForge model policy.
|
|
2
2
|
#
|
|
3
|
-
# Extends @
|
|
3
|
+
# Extends @agent-pattern-labs/iso-route's bundled "standard" preset, then pins every
|
|
4
4
|
# OpenCode route to DeepSeek V4 Flash. Recent traces showed free OpenRouter
|
|
5
5
|
# routes freezing or falling through Venice balance errors, so JobForge's
|
|
6
6
|
# OpenCode default is now "best affordable paid" rather than "free".
|
package/modes/batch.md
CHANGED
|
@@ -68,7 +68,7 @@ batch/batch-runner.sh [OPTIONS]
|
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
`batch-runner.sh` delegates to `scripts/batch-orchestrator.mjs` by default.
|
|
71
|
-
That Node runner uses `@
|
|
71
|
+
That Node runner uses `@agent-pattern-labs/iso-orchestrator` to persist workflow records in
|
|
72
72
|
`.jobforge-runs/`, cap bundle fan-out with `workflow.forEach`, and serialize
|
|
73
73
|
report-number/state writes while workers run in parallel. If a regression
|
|
74
74
|
requires the old shell loop, run with `JOBFORGE_LEGACY_BATCH_RUNNER=1`.
|
package/modes/reference-setup.md
CHANGED
|
@@ -118,7 +118,7 @@ Prefer the deterministic helper:
|
|
|
118
118
|
npx job-forge tracker-line --num 521 --date 2026-04-15 --company "Anthropic" --role "Manager, FDE" --status Evaluated --score 4.2 --pdf no --slug anthropic-manager-fde --notes "Strong fit" --write
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
The helper renders and validates the row against `templates/contracts.json` via `@
|
|
121
|
+
The helper renders and validates the row against `templates/contracts.json` via `@agent-pattern-labs/iso-contract`. To inspect the contract directly:
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
124
|
npx iso-contract explain jobforge.tracker-row --contracts templates/contracts.json
|
package/opencode.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-forge",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.39",
|
|
4
4
|
"description": "AI-powered job search pipeline built on opencode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -167,39 +167,42 @@
|
|
|
167
167
|
"author": "Charlie Greenman",
|
|
168
168
|
"repository": {
|
|
169
169
|
"type": "git",
|
|
170
|
-
"url": "https://github.com/
|
|
170
|
+
"url": "git+https://github.com/Agent-Pattern-Labs/JobForge.git"
|
|
171
171
|
},
|
|
172
172
|
"license": "MIT",
|
|
173
173
|
"engines": {
|
|
174
174
|
"node": ">=20.6.0"
|
|
175
175
|
},
|
|
176
|
+
"overrides": {
|
|
177
|
+
"fast-uri": "3.1.2"
|
|
178
|
+
},
|
|
176
179
|
"dependencies": {
|
|
177
|
-
"@
|
|
178
|
-
"@
|
|
179
|
-
"@
|
|
180
|
-
"@
|
|
181
|
-
"@
|
|
182
|
-
"@
|
|
183
|
-
"@
|
|
184
|
-
"@
|
|
185
|
-
"@
|
|
186
|
-
"@
|
|
187
|
-
"@
|
|
188
|
-
"@
|
|
189
|
-
"@
|
|
190
|
-
"@
|
|
191
|
-
"@
|
|
192
|
-
"@
|
|
193
|
-
"@
|
|
194
|
-
"@
|
|
195
|
-
"@
|
|
180
|
+
"@agent-pattern-labs/iso-cache": "^0.1.1",
|
|
181
|
+
"@agent-pattern-labs/iso-canon": "^0.1.1",
|
|
182
|
+
"@agent-pattern-labs/iso-capabilities": "^0.1.1",
|
|
183
|
+
"@agent-pattern-labs/iso-context": "^0.1.1",
|
|
184
|
+
"@agent-pattern-labs/iso-contract": "^0.1.1",
|
|
185
|
+
"@agent-pattern-labs/iso-facts": "^0.1.1",
|
|
186
|
+
"@agent-pattern-labs/iso-guard": "^0.1.1",
|
|
187
|
+
"@agent-pattern-labs/iso-index": "^0.1.1",
|
|
188
|
+
"@agent-pattern-labs/iso-ledger": "^0.1.1",
|
|
189
|
+
"@agent-pattern-labs/iso-lineage": "^0.1.1",
|
|
190
|
+
"@agent-pattern-labs/iso-migrate": "^0.1.1",
|
|
191
|
+
"@agent-pattern-labs/iso-orchestrator": "^0.2.1",
|
|
192
|
+
"@agent-pattern-labs/iso-postflight": "^0.1.1",
|
|
193
|
+
"@agent-pattern-labs/iso-preflight": "^0.1.1",
|
|
194
|
+
"@agent-pattern-labs/iso-prioritize": "^0.1.1",
|
|
195
|
+
"@agent-pattern-labs/iso-redact": "^0.1.1",
|
|
196
|
+
"@agent-pattern-labs/iso-score": "^0.1.1",
|
|
197
|
+
"@agent-pattern-labs/iso-timeline": "^0.1.1",
|
|
198
|
+
"@agent-pattern-labs/iso-trace": "^0.5.1",
|
|
196
199
|
"playwright": "^1.58.1"
|
|
197
200
|
},
|
|
198
201
|
"devDependencies": {
|
|
199
|
-
"@
|
|
200
|
-
"@
|
|
201
|
-
"@
|
|
202
|
-
"@
|
|
203
|
-
"@
|
|
202
|
+
"@agent-pattern-labs/agentmd": "^0.3.1",
|
|
203
|
+
"@agent-pattern-labs/iso": "^0.3.2",
|
|
204
|
+
"@agent-pattern-labs/iso-eval": "^0.4.1",
|
|
205
|
+
"@agent-pattern-labs/iso-harness": "^0.8.1",
|
|
206
|
+
"@agent-pattern-labs/iso-route": "^0.6.1"
|
|
204
207
|
}
|
|
205
208
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Durable JobForge batch runner powered by @
|
|
3
|
+
* Durable JobForge batch runner powered by @agent-pattern-labs/iso-orchestrator.
|
|
4
4
|
*
|
|
5
5
|
* This preserves the public batch-runner.sh interface while moving the
|
|
6
6
|
* load-bearing control loop into a resumable workflow:
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - idempotent bundle execution keyed by URL + retry count
|
|
9
9
|
* - bounded fan-out through workflow.forEach(..., { maxParallel })
|
|
10
10
|
* - mutexed state/report-number writes across parallel workers
|
|
11
|
+
* - renewable leases + heartbeats for worker liveness inspection
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import { spawn, spawnSync } from 'node:child_process';
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
import { dirname, join, resolve } from 'node:path';
|
|
25
26
|
import { fileURLToPath } from 'node:url';
|
|
26
27
|
|
|
27
|
-
import { runWorkflow } from '@
|
|
28
|
+
import { runWorkflow } from '@agent-pattern-labs/iso-orchestrator';
|
|
28
29
|
|
|
29
30
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
31
|
const PKG_ROOT = resolve(__dirname, '..');
|
|
@@ -46,12 +47,13 @@ const DEFAULT_WORKFLOW_ID = 'jobforge-batch';
|
|
|
46
47
|
const STATE_HEADER = 'id\turl\tstatus\tstarted_at\tcompleted_at\treport_num\tscore\terror\tretries';
|
|
47
48
|
|
|
48
49
|
function usage() {
|
|
49
|
-
console.log(`job-forge batch runner - process job offers in batch via
|
|
50
|
-
Uses
|
|
50
|
+
console.log(`job-forge batch runner - process job offers in batch via AI CLI workers
|
|
51
|
+
Uses the selected runner's default project configuration.
|
|
51
52
|
|
|
52
53
|
Usage: batch-runner.sh [OPTIONS]
|
|
53
54
|
|
|
54
55
|
Options:
|
|
56
|
+
--runner NAME Worker CLI: opencode or codex (default: ${process.env.JOBFORGE_BATCH_RUNNER || 'opencode'})
|
|
55
57
|
--parallel N Number of parallel workers (default: 1)
|
|
56
58
|
--bundle-size N Offers per worker invocation (default: 5, use 1 for
|
|
57
59
|
legacy per-offer mode). Each worker processes N
|
|
@@ -74,6 +76,7 @@ Files:
|
|
|
74
76
|
|
|
75
77
|
function parseArgs(argv) {
|
|
76
78
|
const options = {
|
|
79
|
+
runner: parseRunner(process.env.JOBFORGE_BATCH_RUNNER || 'opencode'),
|
|
77
80
|
parallel: 1,
|
|
78
81
|
dryRun: false,
|
|
79
82
|
retryFailed: false,
|
|
@@ -94,6 +97,9 @@ function parseArgs(argv) {
|
|
|
94
97
|
};
|
|
95
98
|
|
|
96
99
|
switch (arg) {
|
|
100
|
+
case '--runner':
|
|
101
|
+
options.runner = parseRunner(next());
|
|
102
|
+
break;
|
|
97
103
|
case '--parallel':
|
|
98
104
|
options.parallel = parsePositiveInt(next(), '--parallel');
|
|
99
105
|
break;
|
|
@@ -128,6 +134,12 @@ function parseArgs(argv) {
|
|
|
128
134
|
return options;
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
function parseRunner(value) {
|
|
138
|
+
const runner = String(value || '').trim().toLowerCase();
|
|
139
|
+
if (runner === 'opencode' || runner === 'codex') return runner;
|
|
140
|
+
throw new Error(`--runner must be one of: opencode, codex`);
|
|
141
|
+
}
|
|
142
|
+
|
|
131
143
|
function parsePositiveInt(value, label) {
|
|
132
144
|
const n = Number.parseInt(value, 10);
|
|
133
145
|
if (!Number.isInteger(n) || n < 1) {
|
|
@@ -180,7 +192,7 @@ async function readTextIfExists(path) {
|
|
|
180
192
|
return readFile(path, 'utf8');
|
|
181
193
|
}
|
|
182
194
|
|
|
183
|
-
async function checkPrerequisites({ dryRun }) {
|
|
195
|
+
async function checkPrerequisites({ dryRun, runner }) {
|
|
184
196
|
if (!existsSync(INPUT_FILE)) {
|
|
185
197
|
throw new Error(`${INPUT_FILE} not found. Add offers first.`);
|
|
186
198
|
}
|
|
@@ -188,9 +200,10 @@ async function checkPrerequisites({ dryRun }) {
|
|
|
188
200
|
throw new Error(`${PROMPT_FILE} not found.`);
|
|
189
201
|
}
|
|
190
202
|
if (!dryRun) {
|
|
191
|
-
const
|
|
203
|
+
const command = workerCommandName(runner);
|
|
204
|
+
const result = spawnSync(command, ['--help'], { stdio: 'ignore' });
|
|
192
205
|
if (result.error?.code === 'ENOENT') {
|
|
193
|
-
throw new Error(
|
|
206
|
+
throw new Error(`'${command}' CLI not found in PATH.`);
|
|
194
207
|
}
|
|
195
208
|
}
|
|
196
209
|
|
|
@@ -531,6 +544,70 @@ async function runOpencode(prompt, logFile) {
|
|
|
531
544
|
});
|
|
532
545
|
}
|
|
533
546
|
|
|
547
|
+
let batchTemplateCache;
|
|
548
|
+
|
|
549
|
+
async function batchTemplateText() {
|
|
550
|
+
if (batchTemplateCache === undefined) {
|
|
551
|
+
batchTemplateCache = await readFile(PROMPT_FILE, 'utf8');
|
|
552
|
+
}
|
|
553
|
+
return batchTemplateCache;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function workerCommandName(runner) {
|
|
557
|
+
return runner === 'codex' ? 'codex' : 'opencode';
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function runCodex(prompt, logFile) {
|
|
561
|
+
await ensureDir(dirname(logFile));
|
|
562
|
+
const finalMessageFile = `${logFile}.last-message.txt`;
|
|
563
|
+
const combinedPrompt = `${(await batchTemplateText()).trim()}\n\n${prompt}`;
|
|
564
|
+
|
|
565
|
+
return new Promise((resolveRun) => {
|
|
566
|
+
const child = spawn('codex', [
|
|
567
|
+
'exec',
|
|
568
|
+
'--dangerously-bypass-approvals-and-sandbox',
|
|
569
|
+
'-C',
|
|
570
|
+
PROJECT_DIR,
|
|
571
|
+
'--output-last-message',
|
|
572
|
+
finalMessageFile,
|
|
573
|
+
combinedPrompt,
|
|
574
|
+
], {
|
|
575
|
+
cwd: PROJECT_DIR,
|
|
576
|
+
env: {
|
|
577
|
+
...process.env,
|
|
578
|
+
JOB_FORGE_PROJECT: PROJECT_DIR,
|
|
579
|
+
},
|
|
580
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const chunks = [];
|
|
584
|
+
child.stdout.on('data', (chunk) => chunks.push(chunk));
|
|
585
|
+
child.stderr.on('data', (chunk) => chunks.push(chunk));
|
|
586
|
+
|
|
587
|
+
child.on('error', async (error) => {
|
|
588
|
+
chunks.push(Buffer.from(`\n${error.stack || error.message}\n`));
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
child.on('close', async (code) => {
|
|
592
|
+
const output = Buffer.concat(chunks).toString('utf8');
|
|
593
|
+
const finalMessage = await readTextIfExists(finalMessageFile);
|
|
594
|
+
const logOutput = finalMessage
|
|
595
|
+
? `${output}\n\n--- FINAL MESSAGE ---\n${finalMessage}`
|
|
596
|
+
: output;
|
|
597
|
+
await writeFile(logFile, logOutput, 'utf8');
|
|
598
|
+
resolveRun({
|
|
599
|
+
exitCode: code ?? 1,
|
|
600
|
+
output: finalMessage || output,
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function runWorker(runner, prompt, logFile) {
|
|
607
|
+
if (runner === 'codex') return runCodex(prompt, logFile);
|
|
608
|
+
return runOpencode(prompt, logFile);
|
|
609
|
+
}
|
|
610
|
+
|
|
534
611
|
function parseStatusLines(output) {
|
|
535
612
|
const seen = new Map();
|
|
536
613
|
for (const line of output.split('\n')) {
|
|
@@ -550,7 +627,57 @@ function parseStatusLines(output) {
|
|
|
550
627
|
return seen;
|
|
551
628
|
}
|
|
552
629
|
|
|
553
|
-
async function
|
|
630
|
+
async function withWorkerLiveness(workflow, { runner, tag, ids, logFile }, run) {
|
|
631
|
+
const leaseKey = `worker:${tag}`;
|
|
632
|
+
const holder = `${runner}:${process.pid}:${tag}`;
|
|
633
|
+
const detail = {
|
|
634
|
+
runner,
|
|
635
|
+
ids,
|
|
636
|
+
log: relativeProjectPath(logFile),
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
await workflow.touchLease(leaseKey, {
|
|
640
|
+
holder,
|
|
641
|
+
ttlMs: 120_000,
|
|
642
|
+
detail: {
|
|
643
|
+
...detail,
|
|
644
|
+
phase: 'starting',
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
await workflow.heartbeat(leaseKey, {
|
|
648
|
+
...detail,
|
|
649
|
+
phase: 'starting',
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
const timer = setInterval(() => {
|
|
653
|
+
workflow.touchLease(leaseKey, {
|
|
654
|
+
holder,
|
|
655
|
+
ttlMs: 120_000,
|
|
656
|
+
detail: {
|
|
657
|
+
...detail,
|
|
658
|
+
phase: 'running',
|
|
659
|
+
},
|
|
660
|
+
}).catch(() => {});
|
|
661
|
+
workflow.heartbeat(leaseKey, {
|
|
662
|
+
...detail,
|
|
663
|
+
phase: 'running',
|
|
664
|
+
}).catch(() => {});
|
|
665
|
+
}, 30_000);
|
|
666
|
+
timer.unref?.();
|
|
667
|
+
|
|
668
|
+
try {
|
|
669
|
+
return await run();
|
|
670
|
+
} finally {
|
|
671
|
+
clearInterval(timer);
|
|
672
|
+
await workflow.heartbeat(leaseKey, {
|
|
673
|
+
...detail,
|
|
674
|
+
phase: 'finished',
|
|
675
|
+
}).catch(() => {});
|
|
676
|
+
await workflow.releaseLease(leaseKey, holder).catch(() => {});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async function processBundle(workflow, bundle, options) {
|
|
554
681
|
const startedAt = nowIso();
|
|
555
682
|
const specs = await reserveBundle(workflow, bundle, startedAt);
|
|
556
683
|
const tag = bundleTag(bundle);
|
|
@@ -561,11 +688,17 @@ async function processBundle(workflow, bundle) {
|
|
|
561
688
|
type: 'batch.bundle.started',
|
|
562
689
|
detail: {
|
|
563
690
|
ids: bundle.map((offer) => offer.id),
|
|
691
|
+
runner: options.runner,
|
|
564
692
|
log: relativeProjectPath(logFile),
|
|
565
693
|
},
|
|
566
694
|
});
|
|
567
695
|
|
|
568
|
-
const { exitCode, output } = await
|
|
696
|
+
const { exitCode, output } = await withWorkerLiveness(workflow, {
|
|
697
|
+
runner: options.runner,
|
|
698
|
+
tag,
|
|
699
|
+
ids: bundle.map((offer) => offer.id),
|
|
700
|
+
logFile,
|
|
701
|
+
}, () => runWorker(options.runner, buildBundlePrompt(specs), logFile));
|
|
569
702
|
const completedAt = nowIso();
|
|
570
703
|
const statuses = parseStatusLines(output);
|
|
571
704
|
const outcomes = [];
|
|
@@ -598,6 +731,13 @@ async function processBundle(workflow, bundle) {
|
|
|
598
731
|
score: sanitizeCell(score),
|
|
599
732
|
report_num: sanitizeCell(parsed.report_num, spec.report_num),
|
|
600
733
|
});
|
|
734
|
+
await workflow.heartbeat(`worker:${tag}`, {
|
|
735
|
+
runner: options.runner,
|
|
736
|
+
ids: bundle.map((candidate) => candidate.id),
|
|
737
|
+
offerId: spec.id,
|
|
738
|
+
phase: 'settling',
|
|
739
|
+
status,
|
|
740
|
+
}).catch(() => {});
|
|
601
741
|
console.log(` ${status === 'completed' ? 'OK' : 'FAIL'} #${spec.id} (status=${status}, score=${sanitizeCell(score)}, report=${sanitizeCell(parsed.report_num, spec.report_num)})`);
|
|
602
742
|
continue;
|
|
603
743
|
}
|
|
@@ -622,6 +762,13 @@ async function processBundle(workflow, bundle) {
|
|
|
622
762
|
score: '-',
|
|
623
763
|
report_num: spec.report_num,
|
|
624
764
|
});
|
|
765
|
+
await workflow.heartbeat(`worker:${tag}`, {
|
|
766
|
+
runner: options.runner,
|
|
767
|
+
ids: bundle.map((candidate) => candidate.id),
|
|
768
|
+
offerId: spec.id,
|
|
769
|
+
phase: 'settling',
|
|
770
|
+
status: 'failed',
|
|
771
|
+
}).catch(() => {});
|
|
625
772
|
console.log(` FAIL #${spec.id} (no status emitted; see ${relativeProjectPath(logFile)})`);
|
|
626
773
|
}
|
|
627
774
|
|
|
@@ -633,6 +780,7 @@ async function processBundle(workflow, bundle) {
|
|
|
633
780
|
type: 'batch.bundle.completed',
|
|
634
781
|
detail: {
|
|
635
782
|
ids: bundle.map((offer) => offer.id),
|
|
783
|
+
runner: options.runner,
|
|
636
784
|
exitCode,
|
|
637
785
|
log: relativeProjectPath(logFile),
|
|
638
786
|
outcomes,
|
|
@@ -766,7 +914,7 @@ async function run(options) {
|
|
|
766
914
|
const pending = selectPendingOffers(offers, stateRows, options);
|
|
767
915
|
|
|
768
916
|
console.log('=== job-forge batch runner ===');
|
|
769
|
-
console.log(`Parallel: ${options.parallel} | Bundle size: ${options.bundleSize} | Max retries: ${options.maxRetries}`);
|
|
917
|
+
console.log(`Runner: ${options.runner} | Parallel: ${options.parallel} | Bundle size: ${options.bundleSize} | Max retries: ${options.maxRetries}`);
|
|
770
918
|
console.log(`Workflow: ${options.workflowId} (${relativeProjectPath(WORKFLOW_DIR)})`);
|
|
771
919
|
console.log(`Input: ${totalInput} offers`);
|
|
772
920
|
console.log('');
|
|
@@ -812,6 +960,7 @@ async function run(options) {
|
|
|
812
960
|
totalInput,
|
|
813
961
|
pending: pending.length,
|
|
814
962
|
bundles: bundles.length,
|
|
963
|
+
runner: options.runner,
|
|
815
964
|
parallel: options.parallel,
|
|
816
965
|
bundleSize: options.bundleSize,
|
|
817
966
|
},
|
|
@@ -824,7 +973,7 @@ async function run(options) {
|
|
|
824
973
|
const stepName = bundleStepName(bundle, rowsBeforeRun);
|
|
825
974
|
return workflow.step(
|
|
826
975
|
stepName,
|
|
827
|
-
async () => processBundle(workflow, bundle),
|
|
976
|
+
async () => processBundle(workflow, bundle, options),
|
|
828
977
|
{
|
|
829
978
|
idempotencyKey: stepName,
|
|
830
979
|
},
|
package/scripts/cache.mjs
CHANGED
package/scripts/canon.mjs
CHANGED
|
@@ -17,29 +17,29 @@ const migrationScripts = migrationValue('jobforge-managed-scripts', '/scripts');
|
|
|
17
17
|
const migrationIgnores = migrationValue('jobforge-generated-ignores');
|
|
18
18
|
|
|
19
19
|
const groups = [
|
|
20
|
-
helper('trace', '@
|
|
20
|
+
helper('trace', '@agent-pattern-labs/iso-trace', ['list', 'stats', 'show']),
|
|
21
21
|
helper('telemetry', '', ['list', 'status', 'show', 'watch']),
|
|
22
|
-
helper('guard', '@
|
|
23
|
-
helper('ledger', '@
|
|
24
|
-
helper('capabilities', '@
|
|
25
|
-
helper('context', '@
|
|
26
|
-
helper('cache', '@
|
|
27
|
-
helper('index', '@
|
|
28
|
-
helper('facts', '@
|
|
29
|
-
helper('score', '@
|
|
30
|
-
helper('canon', '@
|
|
31
|
-
helper('preflight', '@
|
|
32
|
-
helper('postflight', '@
|
|
33
|
-
helper('timeline', '@
|
|
34
|
-
helper('prioritize', '@
|
|
35
|
-
helper('lineage', '@
|
|
36
|
-
helper('redact', '@
|
|
37
|
-
helper('migrate', '@
|
|
22
|
+
helper('guard', '@agent-pattern-labs/iso-guard', ['audit', 'explain'], { template: 'templates/guards/jobforge-baseline.yaml' }),
|
|
23
|
+
helper('ledger', '@agent-pattern-labs/iso-ledger', ['status', 'rebuild', 'verify', 'has', 'query'], { artifacts: ['.jobforge-ledger/'] }),
|
|
24
|
+
helper('capabilities', '@agent-pattern-labs/iso-capabilities', ['list', 'explain', 'check', 'render'], { template: 'templates/capabilities.json', migrated: true }),
|
|
25
|
+
helper('context', '@agent-pattern-labs/iso-context', ['list', 'explain', 'plan', 'check', 'render'], { template: 'templates/context.json', migrated: true }),
|
|
26
|
+
helper('cache', '@agent-pattern-labs/iso-cache', ['key', 'has', 'get', 'put', 'status', 'list', 'verify', 'prune'], { artifacts: ['.jobforge-cache/'], migrated: true }),
|
|
27
|
+
helper('index', '@agent-pattern-labs/iso-index', ['build', 'status', 'query', 'has', 'verify', 'explain'], { template: 'templates/index.json', artifacts: ['.jobforge-index.json'], migrated: true }),
|
|
28
|
+
helper('facts', '@agent-pattern-labs/iso-facts', ['build', 'status', 'verify', 'check', 'has', 'query', 'explain'], { template: 'templates/facts.json', artifacts: ['.jobforge-facts.json'], migrated: true }),
|
|
29
|
+
helper('score', '@agent-pattern-labs/iso-score', ['compute', 'verify', 'check', 'gate', 'compare', 'explain'], { template: 'templates/score.json', migrated: true }),
|
|
30
|
+
helper('canon', '@agent-pattern-labs/iso-canon', ['normalize', 'key', 'compare', 'explain'], { template: 'templates/canon.json', migrated: true }),
|
|
31
|
+
helper('preflight', '@agent-pattern-labs/iso-preflight', ['plan', 'check', 'explain'], { template: 'templates/preflight.json', artifacts: ['batch/preflight-candidates.json', 'batch/preflight-plan.json'], migrated: true }),
|
|
32
|
+
helper('postflight', '@agent-pattern-labs/iso-postflight', ['status', 'check', 'explain'], { template: 'templates/postflight.json', artifacts: ['batch/postflight-outcomes.json'], migrated: true }),
|
|
33
|
+
helper('timeline', '@agent-pattern-labs/iso-timeline', ['status', 'build', 'plan', 'due', 'check', 'verify', 'explain'], { template: 'templates/timeline.json', artifacts: ['.jobforge-timeline.json', '.jobforge-timeline-events.jsonl', 'data/timeline-events.jsonl'], migrated: true }),
|
|
34
|
+
helper('prioritize', '@agent-pattern-labs/iso-prioritize', ['status', 'items', 'build', 'rank', 'select', 'check', 'verify', 'explain'], { template: 'templates/prioritize.json', artifacts: ['.jobforge-prioritize.json', '.jobforge-prioritize-items.json'], migrated: true }),
|
|
35
|
+
helper('lineage', '@agent-pattern-labs/iso-lineage', ['status', 'record', 'check', 'stale', 'verify', 'explain'], { artifacts: ['.jobforge-lineage.json'], migrated: true }),
|
|
36
|
+
helper('redact', '@agent-pattern-labs/iso-redact', ['scan', 'verify', 'apply', 'explain'], { template: 'templates/redact.json', artifacts: ['.jobforge-redacted/'], migrated: true }),
|
|
37
|
+
helper('migrate', '@agent-pattern-labs/iso-migrate', ['plan', 'apply', 'check', 'explain'], { template: 'templates/migrations.json', migrated: true }),
|
|
38
38
|
];
|
|
39
39
|
|
|
40
40
|
const packageOnly = [
|
|
41
|
-
{ id: 'contract', pkg: '@
|
|
42
|
-
{ id: 'orchestrator', pkg: '@
|
|
41
|
+
{ id: 'contract', pkg: '@agent-pattern-labs/iso-contract', template: 'templates/contracts.json', needles: ['templates/contracts.json', 'tracker-line'] },
|
|
42
|
+
{ id: 'orchestrator', pkg: '@agent-pattern-labs/iso-orchestrator', file: 'scripts/batch-orchestrator.mjs', needles: ['iso-orchestrator'] },
|
|
43
43
|
];
|
|
44
44
|
|
|
45
45
|
const errors = [];
|
|
@@ -5,6 +5,7 @@ import { resolve } from "node:path";
|
|
|
5
5
|
const root = resolve(process.argv[2] ?? ".");
|
|
6
6
|
const files = {
|
|
7
7
|
instructions: readFileSync(resolve(root, "iso/instructions.md"), "utf8"),
|
|
8
|
+
instructionsOpencode: readFileSync(resolve(root, "iso/instructions.opencode.md"), "utf8"),
|
|
8
9
|
helpers: readFileSync(resolve(root, "modes/reference-local-helpers.md"), "utf8"),
|
|
9
10
|
apply: readFileSync(resolve(root, "modes/apply.md"), "utf8"),
|
|
10
11
|
models: readFileSync(resolve(root, "models.yaml"), "utf8"),
|
|
@@ -21,6 +22,7 @@ const checks = [
|
|
|
21
22
|
["H6 requires merge and verify", () => every(files.instructions, ["batch/tracker-additions/*.tsv", "npx job-forge merge", "npx job-forge verify"])],
|
|
22
23
|
["H7 distrusts subagent prose", () => every(files.instructions, ["must originate from a file", "not from prior subagent prose"])],
|
|
23
24
|
["H8 keeps proxy secret and requires stealth", () => every(files.instructions, ["[H8]", "Do not transcribe `server`, `username`, `password`, or `bypass`", "`stealth: true`"])],
|
|
25
|
+
["OpenCode addendum exists for task semantics", () => every(files.instructionsOpencode, ["OpenCode", "`task`", "launch acknowledgement", "Do not use `task` to poll status"])],
|
|
24
26
|
["root points to consolidated helper reference", () => every(files.instructions, ["[D8]", "modes/reference-local-helpers.md", "deterministic local helpers"])],
|
|
25
27
|
["helper reference covers score/timeline/prioritize/lineage", () => every(files.helpers, ["templates/score.json", "npx job-forge score:*", "templates/timeline.json", "npx job-forge timeline:*", "templates/prioritize.json", "npx job-forge prioritize:*", ".jobforge-lineage.json", "npx job-forge lineage:*"])],
|
|
26
28
|
["root helper defaults are consolidated", () => !/\[D(?:9|1\d|2[0-9])\]/.test(files.instructions)],
|