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.
Files changed (58) hide show
  1. package/.codex/config.toml +1 -1
  2. package/.cursor/rules/main.mdc +1 -1
  3. package/.opencode/instructions.md +8 -0
  4. package/AGENTS.md +1 -1
  5. package/CLAUDE.md +1 -1
  6. package/README.md +5 -5
  7. package/batch/README.md +8 -7
  8. package/batch/batch-runner.sh +2 -2
  9. package/bin/create-job-forge.mjs +7 -4
  10. package/bin/sync.mjs +36 -2
  11. package/docs/ARCHITECTURE.md +27 -26
  12. package/docs/CUSTOMIZATION.md +25 -25
  13. package/docs/README.md +1 -1
  14. package/docs/SETUP.md +2 -2
  15. package/iso/instructions.md +1 -1
  16. package/iso/instructions.opencode.md +8 -0
  17. package/lib/jobforge-cache.mjs +1 -1
  18. package/lib/jobforge-canon.mjs +1 -1
  19. package/lib/jobforge-capabilities.mjs +1 -1
  20. package/lib/jobforge-context.mjs +1 -1
  21. package/lib/jobforge-contracts.mjs +1 -1
  22. package/lib/jobforge-facts.mjs +1 -1
  23. package/lib/jobforge-index.mjs +1 -1
  24. package/lib/jobforge-ledger.mjs +1 -1
  25. package/lib/jobforge-lineage.mjs +1 -1
  26. package/lib/jobforge-migrate.mjs +1 -1
  27. package/lib/jobforge-observability.mjs +847 -0
  28. package/lib/jobforge-postflight.mjs +1 -1
  29. package/lib/jobforge-preflight.mjs +1 -1
  30. package/lib/jobforge-prioritize.mjs +1 -1
  31. package/lib/jobforge-redact.mjs +1 -1
  32. package/lib/jobforge-score.mjs +1 -1
  33. package/lib/jobforge-timeline.mjs +1 -1
  34. package/models.yaml +1 -1
  35. package/modes/batch.md +1 -1
  36. package/modes/reference-setup.md +1 -1
  37. package/opencode.json +2 -1
  38. package/package.json +29 -26
  39. package/scripts/batch-orchestrator.mjs +160 -11
  40. package/scripts/cache.mjs +1 -1
  41. package/scripts/canon.mjs +1 -1
  42. package/scripts/check-helper-integration.mjs +19 -19
  43. package/scripts/check-iso-smoke.mjs +2 -0
  44. package/scripts/facts.mjs +1 -1
  45. package/scripts/guard.mjs +115 -191
  46. package/scripts/index.mjs +1 -1
  47. package/scripts/ledger.mjs +1 -1
  48. package/scripts/lineage.mjs +1 -1
  49. package/scripts/migrate.mjs +1 -1
  50. package/scripts/postflight.mjs +1 -1
  51. package/scripts/preflight.mjs +1 -1
  52. package/scripts/prioritize.mjs +1 -1
  53. package/scripts/redact.mjs +1 -1
  54. package/scripts/score.mjs +1 -1
  55. package/scripts/telemetry.mjs +214 -450
  56. package/scripts/timeline.mjs +1 -1
  57. package/scripts/trace.mjs +104 -233
  58. package/templates/portals.example.yml +1 -1
@@ -4,7 +4,7 @@ import {
4
4
  loadPostflightConfig,
5
5
  parseJson,
6
6
  settlePostflight,
7
- } from '@razroo/iso-postflight';
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';
@@ -4,7 +4,7 @@ import {
4
4
  loadPreflightConfig,
5
5
  parseJson,
6
6
  planPreflight,
7
- } from '@razroo/iso-preflight';
7
+ } from '@agent-pattern-labs/iso-preflight';
8
8
 
9
9
  export const PREFLIGHT_CONFIG_FILE = 'templates/preflight.json';
10
10
  export const PREFLIGHT_WORKFLOW = 'jobforge.apply';
@@ -7,7 +7,7 @@ import {
7
7
  prioritize,
8
8
  selectPrioritized,
9
9
  verifyPrioritizeResult,
10
- } from '@razroo/iso-prioritize';
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
 
@@ -3,7 +3,7 @@ import { join } from 'path';
3
3
  import {
4
4
  loadRedactConfig,
5
5
  parseJson,
6
- } from '@razroo/iso-redact';
6
+ } from '@agent-pattern-labs/iso-redact';
7
7
 
8
8
  export const REDACT_CONFIG_FILE = 'templates/redact.json';
9
9
 
@@ -8,7 +8,7 @@ import {
8
8
  parseJson,
9
9
  scoreResultId,
10
10
  verifyScoreResult,
11
- } from '@razroo/iso-score';
11
+ } from '@agent-pattern-labs/iso-score';
12
12
 
13
13
  export const SCORE_CONFIG_FILE = 'templates/score.json';
14
14
  export const SCORE_PROFILE = 'jobforge';
@@ -8,7 +8,7 @@ import {
8
8
  parseJsonLines,
9
9
  planTimeline,
10
10
  verifyTimelineResult,
11
- } from '@razroo/iso-timeline';
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 @razroo/iso-route's bundled "standard" preset, then pins every
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 `@razroo/iso-orchestrator` to persist workflow records in
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`.
@@ -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 `@razroo/iso-contract`. To inspect the contract directly:
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
@@ -46,7 +46,8 @@
46
46
  }
47
47
  },
48
48
  "instructions": [
49
- "templates/states.yml"
49
+ "templates/states.yml",
50
+ ".opencode/instructions.md"
50
51
  ],
51
52
  "small_model": "opencode-go/deepseek-v4-flash"
52
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job-forge",
3
- "version": "2.14.37",
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/razroo/JobForge"
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
- "@razroo/iso-cache": "^0.1.0",
178
- "@razroo/iso-canon": "^0.1.0",
179
- "@razroo/iso-capabilities": "^0.1.0",
180
- "@razroo/iso-context": "^0.1.0",
181
- "@razroo/iso-contract": "^0.1.0",
182
- "@razroo/iso-facts": "^0.1.0",
183
- "@razroo/iso-guard": "^0.1.0",
184
- "@razroo/iso-index": "^0.1.0",
185
- "@razroo/iso-ledger": "^0.1.0",
186
- "@razroo/iso-lineage": "^0.1.0",
187
- "@razroo/iso-migrate": "^0.1.0",
188
- "@razroo/iso-orchestrator": "^0.1.0",
189
- "@razroo/iso-postflight": "^0.1.0",
190
- "@razroo/iso-preflight": "^0.1.0",
191
- "@razroo/iso-prioritize": "^0.1.0",
192
- "@razroo/iso-redact": "^0.1.0",
193
- "@razroo/iso-score": "^0.1.0",
194
- "@razroo/iso-timeline": "^0.1.0",
195
- "@razroo/iso-trace": "^0.4.0",
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
- "@razroo/agentmd": "^0.3.0",
200
- "@razroo/iso": "^0.2.5",
201
- "@razroo/iso-eval": "^0.4.0",
202
- "@razroo/iso-harness": "^0.6.1",
203
- "@razroo/iso-route": "^0.5.3"
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 @razroo/iso-orchestrator.
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 '@razroo/iso-orchestrator';
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 opencode run workers
50
- Uses your default opencode model.
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 result = spawnSync('opencode', ['--help'], { stdio: 'ignore' });
203
+ const command = workerCommandName(runner);
204
+ const result = spawnSync(command, ['--help'], { stdio: 'ignore' });
192
205
  if (result.error?.code === 'ENOENT') {
193
- throw new Error("'opencode' CLI not found in PATH.");
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 processBundle(workflow, bundle) {
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 runOpencode(buildBundlePrompt(specs), logFile);
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
@@ -5,7 +5,7 @@ import {
5
5
  formatCacheEntries,
6
6
  formatPruneResult,
7
7
  formatVerifyResult,
8
- } from '@razroo/iso-cache';
8
+ } from '@agent-pattern-labs/iso-cache';
9
9
  import { PROJECT_DIR } from '../tracker-lib.mjs';
10
10
  import {
11
11
  DEFAULT_JD_TTL_MS,
package/scripts/canon.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  formatCanonResult,
6
6
  formatCompareResult,
7
7
  formatConfigSummary,
8
- } from '@razroo/iso-canon';
8
+ } from '@agent-pattern-labs/iso-canon';
9
9
  import { PROJECT_DIR } from '../tracker-lib.mjs';
10
10
  import {
11
11
  canonicalizeJobForgeEntity,
@@ -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', '@razroo/iso-trace', ['list', 'stats', 'show']),
20
+ helper('trace', '@agent-pattern-labs/iso-trace', ['list', 'stats', 'show']),
21
21
  helper('telemetry', '', ['list', 'status', 'show', 'watch']),
22
- helper('guard', '@razroo/iso-guard', ['audit', 'explain'], { template: 'templates/guards/jobforge-baseline.yaml' }),
23
- helper('ledger', '@razroo/iso-ledger', ['status', 'rebuild', 'verify', 'has', 'query'], { artifacts: ['.jobforge-ledger/'] }),
24
- helper('capabilities', '@razroo/iso-capabilities', ['list', 'explain', 'check', 'render'], { template: 'templates/capabilities.json', migrated: true }),
25
- helper('context', '@razroo/iso-context', ['list', 'explain', 'plan', 'check', 'render'], { template: 'templates/context.json', migrated: true }),
26
- helper('cache', '@razroo/iso-cache', ['key', 'has', 'get', 'put', 'status', 'list', 'verify', 'prune'], { artifacts: ['.jobforge-cache/'], migrated: true }),
27
- helper('index', '@razroo/iso-index', ['build', 'status', 'query', 'has', 'verify', 'explain'], { template: 'templates/index.json', artifacts: ['.jobforge-index.json'], migrated: true }),
28
- helper('facts', '@razroo/iso-facts', ['build', 'status', 'verify', 'check', 'has', 'query', 'explain'], { template: 'templates/facts.json', artifacts: ['.jobforge-facts.json'], migrated: true }),
29
- helper('score', '@razroo/iso-score', ['compute', 'verify', 'check', 'gate', 'compare', 'explain'], { template: 'templates/score.json', migrated: true }),
30
- helper('canon', '@razroo/iso-canon', ['normalize', 'key', 'compare', 'explain'], { template: 'templates/canon.json', migrated: true }),
31
- helper('preflight', '@razroo/iso-preflight', ['plan', 'check', 'explain'], { template: 'templates/preflight.json', artifacts: ['batch/preflight-candidates.json', 'batch/preflight-plan.json'], migrated: true }),
32
- helper('postflight', '@razroo/iso-postflight', ['status', 'check', 'explain'], { template: 'templates/postflight.json', artifacts: ['batch/postflight-outcomes.json'], migrated: true }),
33
- helper('timeline', '@razroo/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', '@razroo/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', '@razroo/iso-lineage', ['status', 'record', 'check', 'stale', 'verify', 'explain'], { artifacts: ['.jobforge-lineage.json'], migrated: true }),
36
- helper('redact', '@razroo/iso-redact', ['scan', 'verify', 'apply', 'explain'], { template: 'templates/redact.json', artifacts: ['.jobforge-redacted/'], migrated: true }),
37
- helper('migrate', '@razroo/iso-migrate', ['plan', 'apply', 'check', 'explain'], { template: 'templates/migrations.json', migrated: true }),
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: '@razroo/iso-contract', template: 'templates/contracts.json', needles: ['templates/contracts.json', 'tracker-line'] },
42
- { id: 'orchestrator', pkg: '@razroo/iso-orchestrator', file: 'scripts/batch-orchestrator.mjs', needles: ['iso-orchestrator'] },
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)],
package/scripts/facts.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  formatConfigSummary,
8
8
  formatFacts,
9
9
  formatVerifyResult,
10
- } from '@razroo/iso-facts';
10
+ } from '@agent-pattern-labs/iso-facts';
11
11
  import { PROJECT_DIR } from '../tracker-lib.mjs';
12
12
  import {
13
13
  buildJobForgeFacts,