job-forge 2.14.19 → 2.14.21

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.
@@ -68,11 +68,14 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
68
68
  - [D10] Treat `templates/capabilities.json` as the source of truth for role capability boundaries. Use `npx job-forge capabilities:explain <role>` or `npx job-forge capabilities:check <role> ...` when changing or validating subagent tool/MCP/filesystem/command permissions; do not paste the full capability matrix into task prompts.
69
69
  why: executable local policy prevents role-permission drift without adding MCP/tool-schema tokens or loading a capability matrix into the shared prefix
70
70
 
71
+ - [D11] Treat `templates/context.json` as the source of truth for mode/reference context bundles. Use `npx job-forge context:plan <mode>` or `npx job-forge context:check <mode>` when changing or validating what a mode loads; do not paste the full context matrix into prompts.
72
+ why: deterministic context bundles prevent reference-file drift and accidental token bloat without adding MCP/tool-schema tokens
73
+
71
74
  ## Procedure
72
75
 
73
76
  1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
74
77
  2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
75
- 3. Read the active mode file [D3]; decide inline vs delegated work [D1].
78
+ 3. Read the active mode file [D3]; use context bundle checks when changing context loads [D11]; decide inline vs delegated work [D1].
76
79
  4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
77
80
  5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
78
81
  6. Keep multi-job form-filling out of the orchestrator [H4].
@@ -80,6 +80,10 @@ Artifact contracts (terminal, outside opencode):
80
80
  Role capabilities (terminal, outside opencode):
81
81
  npx job-forge capabilities:explain general-free
82
82
  npx job-forge capabilities:check general-free --tool browser --mcp geometra --filesystem write
83
+
84
+ Context bundles (terminal, outside opencode):
85
+ npx job-forge context:plan apply
86
+ npx job-forge context:check apply --budget 23000
83
87
  ```
84
88
 
85
89
  ---
package/AGENTS.md CHANGED
@@ -63,11 +63,14 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
63
63
  - [D10] Treat `templates/capabilities.json` as the source of truth for role capability boundaries. Use `npx job-forge capabilities:explain <role>` or `npx job-forge capabilities:check <role> ...` when changing or validating subagent tool/MCP/filesystem/command permissions; do not paste the full capability matrix into task prompts.
64
64
  why: executable local policy prevents role-permission drift without adding MCP/tool-schema tokens or loading a capability matrix into the shared prefix
65
65
 
66
+ - [D11] Treat `templates/context.json` as the source of truth for mode/reference context bundles. Use `npx job-forge context:plan <mode>` or `npx job-forge context:check <mode>` when changing or validating what a mode loads; do not paste the full context matrix into prompts.
67
+ why: deterministic context bundles prevent reference-file drift and accidental token bloat without adding MCP/tool-schema tokens
68
+
66
69
  ## Procedure
67
70
 
68
71
  1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
69
72
  2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
70
- 3. Read the active mode file [D3]; decide inline vs delegated work [D1].
73
+ 3. Read the active mode file [D3]; use context bundle checks when changing context loads [D11]; decide inline vs delegated work [D1].
71
74
  4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
72
75
  5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
73
76
  6. Keep multi-job form-filling out of the orchestrator [H4].
package/CLAUDE.md CHANGED
@@ -63,11 +63,14 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
63
63
  - [D10] Treat `templates/capabilities.json` as the source of truth for role capability boundaries. Use `npx job-forge capabilities:explain <role>` or `npx job-forge capabilities:check <role> ...` when changing or validating subagent tool/MCP/filesystem/command permissions; do not paste the full capability matrix into task prompts.
64
64
  why: executable local policy prevents role-permission drift without adding MCP/tool-schema tokens or loading a capability matrix into the shared prefix
65
65
 
66
+ - [D11] Treat `templates/context.json` as the source of truth for mode/reference context bundles. Use `npx job-forge context:plan <mode>` or `npx job-forge context:check <mode>` when changing or validating what a mode loads; do not paste the full context matrix into prompts.
67
+ why: deterministic context bundles prevent reference-file drift and accidental token bloat without adding MCP/tool-schema tokens
68
+
66
69
  ## Procedure
67
70
 
68
71
  1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
69
72
  2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
70
- 3. Read the active mode file [D3]; decide inline vs delegated work [D1].
73
+ 3. Read the active mode file [D3]; use context bundle checks when changing context loads [D11]; decide inline vs delegated work [D1].
71
74
  4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
72
75
  5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
73
76
  6. Keep multi-job form-filling out of the orchestrator [H4].
package/README.md CHANGED
@@ -31,7 +31,7 @@ The scaffolded `opencode.json` already has three MCPs wired up — they launch a
31
31
  - **Gmail** — reads replies from recruiters
32
32
  - **state-trace** — typed working memory for cross-session context (resumed batches, recent decisions, repeated portal quirks). Install once with `python3 -m pip install "state-trace[mcp]"`; the MCP command is `state-trace-mcp`.
33
33
 
34
- JobForge also keeps MCP-free local workflow state: `templates/contracts.json` defines tracker/apply artifact shapes via `@razroo/iso-contract`, `templates/capabilities.json` defines role capability boundaries via `@razroo/iso-capabilities`, and `.jobforge-ledger/events.jsonl` records deterministic duplicate/status events via `@razroo/iso-ledger`. None of these add prompt or tool-schema tokens.
34
+ JobForge also keeps MCP-free local workflow state: `templates/contracts.json` defines tracker/apply artifact shapes via `@razroo/iso-contract`, `templates/capabilities.json` defines role capability boundaries via `@razroo/iso-capabilities`, `templates/context.json` defines deterministic mode/reference bundles via `@razroo/iso-context`, and `.jobforge-ledger/events.jsonl` records deterministic duplicate/status events via `@razroo/iso-ledger`. None of these add always-on prompt or tool-schema tokens.
35
35
 
36
36
  `npm install` also materializes symlinks for every supported agent harness — OpenCode, Cursor, Claude Code, and Codex — so you can run `opencode`, `cursor`, `claude`, or `codex` in the same project and each picks up the shared MCP config and instructions.
37
37
 
@@ -78,7 +78,7 @@ JobForge turns opencode into a full job search command center. Instead of manual
78
78
  | **Durable Batch Orchestration** | `batch-runner.sh` uses `@razroo/iso-orchestrator` for resumable bundle execution, bounded fan-out, mutexed state writes, and workflow records in `.jobforge-runs/`. |
79
79
  | **Pipeline Integrity** | Automated merge, dedup, status normalization, health checks |
80
80
  | **Cost-Aware Agent Routing** | Three subagents (`@general-free`, `@general-paid`, `@glm-minimal`) with per-task tool surfaces. On OpenCode, JobForge pins all tiers to `opencode-go/deepseek-v4-flash` so application runs avoid overloaded free-model pools. See [Subagent Routing in AGENTS.md](AGENTS.md) for the task-to-agent mapping. |
81
- | **Trace + Telemetry + Guard + Contract + Ledger + Capabilities** | `job-forge trace:*` exposes local OpenCode transcripts, `job-forge telemetry:*` summarizes runs, `job-forge guard:*` audits deterministic policy rules, `templates/contracts.json` enforces artifact shape with `iso-contract`, `job-forge ledger:*` queries append-only workflow state, and `job-forge capabilities:*` checks role boundaries without MCP/token overhead. |
81
+ | **Trace + Telemetry + Guard + Contract + Ledger + Capabilities + Context** | `job-forge trace:*` exposes local OpenCode transcripts, `job-forge telemetry:*` summarizes runs, `job-forge guard:*` audits deterministic policy rules, `templates/contracts.json` enforces artifact shape with `iso-contract`, `job-forge ledger:*` queries append-only workflow state, `job-forge capabilities:*` checks role boundaries, and `job-forge context:*` plans mode/reference context bundles without MCP/tool-schema overhead. |
82
82
  | **Token Cost Visibility** | `job-forge tokens --days 1` for per-session breakdown; `job-forge session-report --since-minutes 60 --log` to flag sessions over budget and append history to `data/token-usage.tsv`. Auto-logged after every batch run. |
83
83
 
84
84
  ## Usage
@@ -162,7 +162,7 @@ my-search/
162
162
  ├── .opencode/skills/job-forge.md # → skill router
163
163
  ├── .opencode/agents/ # → @general-free, @general-paid, @glm-minimal
164
164
  ├── modes/ # → _shared.md + skill modes
165
- ├── templates/ # → states.yml, portals.example.yml, cv-template.html, capabilities.json
165
+ ├── templates/ # → states.yml, portals.example.yml, cv-template.html, capabilities.json, context.json
166
166
  ├── batch/batch-prompt.md # → batch worker prompt
167
167
  ├── batch/batch-runner.sh # → parallel orchestrator
168
168
 
@@ -188,7 +188,7 @@ JobForge/
188
188
  │ ├── sync.mjs # postinstall: creates symlinks in consumer project
189
189
  │ └── create-job-forge.mjs # scaffolder
190
190
  ├── modes/ # _shared.md + 16 skill modes
191
- ├── templates/ # cv-template.html, portals.example.yml, states.yml, capabilities.json
191
+ ├── templates/ # cv-template.html, portals.example.yml, states.yml, capabilities.json, context.json
192
192
  ├── config/profile.example.yml # template for consumer's profile.yml
193
193
  ├── batch/{batch-prompt.md,batch-runner.sh} # batch orchestrator
194
194
  ├── scripts/
@@ -196,6 +196,7 @@ JobForge/
196
196
  │ ├── tracker-line.mjs # iso-contract-backed tracker TSV renderer
197
197
  │ ├── ledger.mjs # iso-ledger-backed workflow-state CLI
198
198
  │ ├── capabilities.mjs # iso-capabilities-backed role policy CLI
199
+ │ ├── context.mjs # iso-context-backed context bundle CLI
199
200
  │ ├── token-usage-report.mjs # opencode cost analyzer
200
201
  │ └── release/check-source.mjs # version gate for npm publish
201
202
  ├── tracker-lib.mjs / merge-tracker.mjs / dedup-tracker.mjs / verify-pipeline.mjs
package/bin/job-forge.mjs CHANGED
@@ -22,6 +22,7 @@
22
22
  * guard:* Audit JobForge trace policy with iso-guard
23
23
  * ledger:* Query local deterministic workflow state via iso-ledger
24
24
  * capabilities:* Query role capability policy via iso-capabilities
25
+ * context:* Query/render deterministic context bundles via iso-context
25
26
  * sync Re-run the harness symlink sync (bin/sync.mjs)
26
27
  * help, --help Show this message
27
28
  */
@@ -93,6 +94,15 @@ const capabilitiesAliases = {
93
94
  'capabilities:path': 'path',
94
95
  };
95
96
 
97
+ const contextAliases = {
98
+ 'context:list': 'list',
99
+ 'context:explain': 'explain',
100
+ 'context:plan': 'plan',
101
+ 'context:check': 'check',
102
+ 'context:render': 'render',
103
+ 'context:path': 'path',
104
+ };
105
+
96
106
  const [, , cmd, ...rest] = process.argv;
97
107
 
98
108
  function printHelp() {
@@ -127,6 +137,11 @@ Commands:
127
137
  capabilities:explain Explain one role capability policy
128
138
  capabilities:check Validate requested tool/MCP/command/fs/network access
129
139
  capabilities:render Render compact role guidance for an agent harness
140
+ context:list List JobForge context bundles
141
+ context:explain Explain one context bundle
142
+ context:plan Estimate files/tokens for one context bundle
143
+ context:check Fail if a context bundle exceeds its budget
144
+ context:render Render context bundle content as markdown/json
130
145
  sync Re-create harness symlinks in the current project
131
146
 
132
147
  Deterministic helpers (prefer these over LLM-derived values):
@@ -158,6 +173,8 @@ Pass --help after a command to see its own flags, e.g.:
158
173
  job-forge ledger:has --company "Acme" --role "Staff Engineer" --status Applied
159
174
  job-forge capabilities:explain general-free
160
175
  job-forge capabilities:check general-free --tool browser --mcp geometra --command "npx job-forge merge" --filesystem write
176
+ job-forge context:plan apply
177
+ job-forge context:check apply --budget 23000
161
178
 
162
179
  Project directory resolves to $JOB_FORGE_PROJECT or cwd.`);
163
180
  }
@@ -242,6 +259,21 @@ if (cmd === 'capabilities' || capabilitiesAliases[cmd]) {
242
259
  process.exit(result.status ?? 1);
243
260
  }
244
261
 
262
+ if (cmd === 'context' || contextAliases[cmd]) {
263
+ const contextArgs = cmd === 'context'
264
+ ? (rest.length === 0 ? ['help'] : rest)
265
+ : [contextAliases[cmd], ...rest];
266
+
267
+ const scriptPath = join(PKG_ROOT, 'scripts/context.mjs');
268
+ const result = spawnSync(process.execPath, [scriptPath, ...contextArgs], {
269
+ stdio: 'inherit',
270
+ cwd: PROJECT_DIR,
271
+ env: process.env,
272
+ });
273
+
274
+ process.exit(result.status ?? 1);
275
+ }
276
+
245
277
  const rel = commands[cmd];
246
278
  if (!rel) {
247
279
  console.error(`Unknown command: ${cmd}\n`);
@@ -163,6 +163,7 @@ data/pipeline.md → Pending URLs and `local:jds/...` inbox (see modes/p
163
163
  .jobforge-ledger/events.jsonl → Append-only workflow events for cheap local duplicate/status checks
164
164
  jds/*.md → Saved job descriptions referenced from the pipeline (`local:jds/{file}`)
165
165
  templates/states.yml → Canonical status values
166
+ templates/context.json → Deterministic mode/reference context bundle policy
166
167
  templates/cv-template.html → PDF generation template
167
168
  examples/*.md → Fictional layouts only (not read by scripts; see examples/README.md)
168
169
  ```
@@ -176,6 +177,7 @@ Create `data/pipeline.md` when you start using the URL inbox (`/job-forge pipeli
176
177
  - Tracker TSVs: `batch/tracker-additions/{num}-{company-slug}.tsv` (one file per evaluation; merged files move under `batch/tracker-additions/merged/`; shape enforced by `templates/contracts.json`)
177
178
  - Ledger: `.jobforge-ledger/events.jsonl` (created by `job-forge ledger:rebuild`, `tracker-line --write`, or `merge`; gitignored personal state)
178
179
  - Capabilities: `templates/capabilities.json` (role boundary policy inspected with `job-forge capabilities:*`)
180
+ - Context: `templates/context.json` (mode/reference file bundles inspected with `job-forge context:*`)
179
181
 
180
182
  ## Pipeline Integrity
181
183
 
@@ -217,6 +219,7 @@ Scripts maintain data consistency. In a consumer project they're invoked via the
217
219
  | `scripts/telemetry.mjs` | `npx job-forge telemetry:status` / `telemetry:show` | JobForge operational telemetry derived from OpenCode traces plus tracker TSV state |
218
220
  | `scripts/guard.mjs` | `npx job-forge guard:audit` / `guard:explain` | Deterministic `@razroo/iso-guard` policy audits over local OpenCode traces |
219
221
  | `scripts/ledger.mjs` | `npx job-forge ledger:status` / `ledger:has` / `ledger:rebuild` | Deterministic `@razroo/iso-ledger` state over tracker, TSV, and pipeline files |
222
+ | `scripts/context.mjs` | `npx job-forge context:list` / `context:plan` / `context:check` / `context:render` | Deterministic `@razroo/iso-context` mode/reference context bundle planning and rendering |
220
223
  | `tracker-lib.mjs` | _(library)_ | Shared helpers for reading/writing day-based tracker files — imported by merge/dedup/verify/normalize |
221
224
  | `bin/sync.mjs` | `npx job-forge sync` | Creates the harness symlinks in a consumer project (also runs as `postinstall`) |
222
225
  | `bin/create-job-forge.mjs` | `npx create-job-forge <dir>` | Scaffolds a new personal project |
@@ -146,6 +146,10 @@ Machine-readable artifact shapes live in `templates/contracts.json` and are enfo
146
146
 
147
147
  Role capability boundaries live in `templates/capabilities.json` and are enforced locally by `@razroo/iso-capabilities`. Use `job-forge capabilities:explain <role>` to inspect a role and `job-forge capabilities:check <role> ...` to validate a tool, MCP, command, filesystem, or network boundary before changing agent frontmatter. Custom forks can extend the policy, but keep it aligned with `.opencode/agents/` and the routing rules in `iso/instructions.md`.
148
148
 
149
+ ## JobForge context bundles
150
+
151
+ Mode/reference context bundles live in `templates/context.json` and are planned locally by `@razroo/iso-context`. Use `job-forge context:plan <mode>` to see the files and estimated tokens, `job-forge context:check <mode>` to fail on budget drift, and `job-forge context:render <mode>` when you intentionally need a compact markdown or JSON context bundle. This is not an MCP and does not add tool-schema tokens; rendered context only consumes prompt tokens when a workflow deliberately asks for it.
152
+
149
153
  ## JobForge guard audits
150
154
 
151
155
  Guard audits run deterministic `@razroo/iso-guard` policies over the same local OpenCode traces. The default policy lives at `templates/guards/jobforge-baseline.yaml` and checks rules that are reliable from transcript data, including max two task dispatches per assistant message, no task-status polling via `task`, no raw proxy configuration in task prompts, and no child session task recursion.
@@ -55,6 +55,8 @@ Disables ~30 MCP tool schemas globally; each agent re-enables only what it needs
55
55
 
56
56
  `templates/capabilities.json` is the executable source for intended role boundaries. Inspect it with `job-forge capabilities:explain general-free`, or validate a boundary with `job-forge capabilities:check general-free --tool browser --mcp geometra --command "npx job-forge merge" --filesystem write --network restricted`. Native harness frontmatter still enforces permissions where supported; `iso-capabilities` gives JobForge a local audit/check contract for changes and reviews.
57
57
 
58
+ `templates/context.json` is the executable source for intended mode/reference context loads. Inspect a bundle with `job-forge context:plan apply`, fail CI or local reviews on drift with `job-forge context:check apply`, or render the bundle only when a workflow deliberately needs it. `iso-context` is local CLI/library policy, not an MCP, so it avoids tool-schema overhead and prevents accidental "load every mode file" prompt bloat.
59
+
58
60
  **3. Thinking budgets** (`reasoningEffort` in agent frontmatter):
59
61
  - `@general-free`: `minimal` — procedural work shouldn't need chain-of-thought
60
62
  - `@general-paid`: `medium` — writing quality benefits from thinking
package/docs/README.md CHANGED
@@ -31,7 +31,7 @@ The harness exposes a single CLI (`job-forge`) installed as a `bin` entry. In a
31
31
 
32
32
  | What you need | Where to read |
33
33
  |---------------|---------------|
34
- | Full command list (`verify`, `merge`, `dedup`, `normalize`, `pdf`, `sync-check`, `tokens`, `trace`, `telemetry`, `guard`, `ledger`, `sync`). | [SETUP.md — Tracker and scripts (terminal)](SETUP.md#tracker-and-scripts-terminal). |
34
+ | Full command list (`verify`, `merge`, `dedup`, `normalize`, `pdf`, `sync-check`, `tokens`, `trace`, `telemetry`, `guard`, `ledger`, `context`, `sync`). | [SETUP.md — Tracker and scripts (terminal)](SETUP.md#tracker-and-scripts-terminal). |
35
35
  | What each harness `.mjs` script does. | [ARCHITECTURE.md — Pipeline integrity](ARCHITECTURE.md#pipeline-integrity) and the scripts table underneath. |
36
36
  | Batch runner, TSV layout, and `batch/tracker-additions/` merge flow. | [batch/README.md](../batch/README.md). |
37
37
  | PR gate for harness contributions (`npm run verify` + `npm run build:dashboard`). | [CONTRIBUTING.md — Development](../CONTRIBUTING.md#development). |
package/docs/SETUP.md CHANGED
@@ -127,6 +127,7 @@ From your project root, these commands maintain the tracker and pipeline checks.
127
127
  | Merge `batch/tracker-additions/*.tsv` into the tracker | `npx job-forge merge` | `npm run merge` |
128
128
  | Inspect tracker row contract | `npx iso-contract explain jobforge.tracker-row --contracts templates/contracts.json` | _(none)_ |
129
129
  | Inspect role capabilities | `npx job-forge capabilities:explain general-free` | `npm run capabilities:explain -- general-free` |
130
+ | Inspect context bundle budget | `npx job-forge context:plan apply` | `npm run context:plan -- apply` |
130
131
  | Map status column to canonical labels | `npx job-forge normalize` | `npm run normalize` |
131
132
  | Merge duplicate company/role rows | `npx job-forge dedup` | `npm run dedup` |
132
133
  | Generate ATS-optimized CV PDF | `npx job-forge pdf` | `npm run pdf` |
@@ -83,6 +83,10 @@ Artifact contracts (terminal, outside opencode):
83
83
  Role capabilities (terminal, outside opencode):
84
84
  npx job-forge capabilities:explain general-free
85
85
  npx job-forge capabilities:check general-free --tool browser --mcp geometra --filesystem write
86
+
87
+ Context bundles (terminal, outside opencode):
88
+ npx job-forge context:plan apply
89
+ npx job-forge context:check apply --budget 23000
86
90
  ```
87
91
 
88
92
  ---
@@ -63,11 +63,14 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
63
63
  - [D10] Treat `templates/capabilities.json` as the source of truth for role capability boundaries. Use `npx job-forge capabilities:explain <role>` or `npx job-forge capabilities:check <role> ...` when changing or validating subagent tool/MCP/filesystem/command permissions; do not paste the full capability matrix into task prompts.
64
64
  why: executable local policy prevents role-permission drift without adding MCP/tool-schema tokens or loading a capability matrix into the shared prefix
65
65
 
66
+ - [D11] Treat `templates/context.json` as the source of truth for mode/reference context bundles. Use `npx job-forge context:plan <mode>` or `npx job-forge context:check <mode>` when changing or validating what a mode loads; do not paste the full context matrix into prompts.
67
+ why: deterministic context bundles prevent reference-file drift and accidental token bloat without adding MCP/tool-schema tokens
68
+
66
69
  ## Procedure
67
70
 
68
71
  1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
69
72
  2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
70
- 3. Read the active mode file [D3]; decide inline vs delegated work [D1].
73
+ 3. Read the active mode file [D3]; use context bundle checks when changing context loads [D11]; decide inline vs delegated work [D1].
71
74
  4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
72
75
  5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
73
76
  6. Keep multi-job form-filling out of the orchestrator [H4].
@@ -0,0 +1,55 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { dirname, join, resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import {
5
+ bundleNames,
6
+ formatContextPlan,
7
+ formatResolvedContextBundle,
8
+ loadContextPolicy,
9
+ planContext,
10
+ renderContextPlan,
11
+ resolveContextBundle,
12
+ } from '@razroo/iso-context';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const PKG_ROOT = resolve(__dirname, '..');
16
+ export const CONTEXT_RELATIVE_PATH = 'templates/context.json';
17
+
18
+ export function resolveProjectDir(projectDir = process.env.JOB_FORGE_PROJECT || process.cwd()) {
19
+ return projectDir;
20
+ }
21
+
22
+ export function jobForgeContextPath(projectDir = resolveProjectDir()) {
23
+ const projectPath = join(projectDir, CONTEXT_RELATIVE_PATH);
24
+ if (existsSync(projectPath)) return projectPath;
25
+ return join(PKG_ROOT, CONTEXT_RELATIVE_PATH);
26
+ }
27
+
28
+ export function loadJobForgeContextPolicy(projectDir = resolveProjectDir()) {
29
+ const path = jobForgeContextPath(projectDir);
30
+ return loadContextPolicy(JSON.parse(readFileSync(path, 'utf-8')));
31
+ }
32
+
33
+ export function listJobForgeContextBundles(projectDir = resolveProjectDir()) {
34
+ return bundleNames(loadJobForgeContextPolicy(projectDir));
35
+ }
36
+
37
+ export function resolveJobForgeContextBundle(name, projectDir = resolveProjectDir()) {
38
+ return resolveContextBundle(loadJobForgeContextPolicy(projectDir), name);
39
+ }
40
+
41
+ export function planJobForgeContextBundle(name, options = {}, projectDir = resolveProjectDir()) {
42
+ return planContext(loadJobForgeContextPolicy(projectDir), name, options);
43
+ }
44
+
45
+ export function formatJobForgeContextPlan(plan) {
46
+ return formatContextPlan(plan);
47
+ }
48
+
49
+ export function formatJobForgeContextBundle(bundle) {
50
+ return formatResolvedContextBundle(bundle);
51
+ }
52
+
53
+ export function renderJobForgeContextPlan(plan, target = 'markdown') {
54
+ return renderContextPlan(plan, target);
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job-forge",
3
- "version": "2.14.19",
3
+ "version": "2.14.21",
4
4
  "description": "AI-powered job search pipeline built on opencode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,6 +36,11 @@
36
36
  "capabilities:explain": "node bin/job-forge.mjs capabilities:explain",
37
37
  "capabilities:check": "node bin/job-forge.mjs capabilities:check",
38
38
  "capabilities:render": "node bin/job-forge.mjs capabilities:render",
39
+ "context:list": "node bin/job-forge.mjs context:list",
40
+ "context:explain": "node bin/job-forge.mjs context:explain",
41
+ "context:plan": "node bin/job-forge.mjs context:plan",
42
+ "context:check": "node bin/job-forge.mjs context:check",
43
+ "context:render": "node bin/job-forge.mjs context:render",
39
44
  "plan": "iso plan .",
40
45
  "lint:agentmd": "agentmd lint iso/instructions.md",
41
46
  "lint:modes": "isolint lint modes/",
@@ -101,6 +106,7 @@
101
106
  },
102
107
  "dependencies": {
103
108
  "@razroo/iso-capabilities": "^0.1.0",
109
+ "@razroo/iso-context": "^0.1.0",
104
110
  "@razroo/iso-contract": "^0.1.0",
105
111
  "@razroo/iso-guard": "^0.1.0",
106
112
  "@razroo/iso-ledger": "^0.1.0",
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { PROJECT_DIR } from '../tracker-lib.mjs';
4
+ import {
5
+ formatJobForgeContextBundle,
6
+ formatJobForgeContextPlan,
7
+ jobForgeContextPath,
8
+ listJobForgeContextBundles,
9
+ planJobForgeContextBundle,
10
+ renderJobForgeContextPlan,
11
+ resolveJobForgeContextBundle,
12
+ } from '../lib/jobforge-context.mjs';
13
+
14
+ const USAGE = `job-forge context - deterministic mode/reference context policy
15
+
16
+ Usage:
17
+ job-forge context:list [--json]
18
+ job-forge context:explain <bundle> [--json]
19
+ job-forge context:plan <bundle> [--root <dir>] [--budget N] [--chars-per-token N] [--json]
20
+ job-forge context:check <bundle> [--root <dir>] [--budget N] [--chars-per-token N] [--json]
21
+ job-forge context:render <bundle> [--root <dir>] [--target markdown|json] [--json]
22
+ job-forge context:path
23
+
24
+ The policy is templates/context.json. It is local project policy, not an MCP
25
+ and not always-loaded prompt context.`;
26
+
27
+ const [cmd = 'help', ...rawArgs] = process.argv.slice(2);
28
+ const { positional, opts } = parseArgs(rawArgs);
29
+
30
+ if (opts.help || cmd === 'help' || cmd === '--help' || cmd === '-h') {
31
+ console.log(USAGE);
32
+ process.exit(0);
33
+ }
34
+
35
+ try {
36
+ if (cmd === 'path') {
37
+ console.log(jobForgeContextPath(PROJECT_DIR));
38
+ } else if (cmd === 'list') {
39
+ list(opts);
40
+ } else if (cmd === 'explain') {
41
+ explain(positional, opts);
42
+ } else if (cmd === 'plan') {
43
+ plan(positional, opts, false);
44
+ } else if (cmd === 'check') {
45
+ check(positional, opts);
46
+ } else if (cmd === 'render') {
47
+ render(positional, opts);
48
+ } else {
49
+ console.error(`unknown context command "${cmd}"\n`);
50
+ console.error(USAGE);
51
+ process.exit(2);
52
+ }
53
+ } catch (error) {
54
+ console.error(error instanceof Error ? error.message : String(error));
55
+ process.exit(1);
56
+ }
57
+
58
+ function parseArgs(args) {
59
+ const positional = [];
60
+ const opts = {
61
+ json: false,
62
+ help: false,
63
+ target: 'markdown',
64
+ };
65
+
66
+ for (let i = 0; i < args.length; i++) {
67
+ const arg = args[i];
68
+ if (arg === '--json') {
69
+ opts.json = true;
70
+ } else if (arg === '--root') {
71
+ opts.root = valueAfter(args, ++i, '--root');
72
+ } else if (arg.startsWith('--root=')) {
73
+ opts.root = arg.slice('--root='.length);
74
+ } else if (arg === '--budget' || arg === '--token-budget') {
75
+ opts.tokenBudget = parsePositiveInteger(valueAfter(args, ++i, arg), arg);
76
+ } else if (arg.startsWith('--budget=')) {
77
+ opts.tokenBudget = parsePositiveInteger(arg.slice('--budget='.length), '--budget');
78
+ } else if (arg.startsWith('--token-budget=')) {
79
+ opts.tokenBudget = parsePositiveInteger(arg.slice('--token-budget='.length), '--token-budget');
80
+ } else if (arg === '--chars-per-token') {
81
+ opts.charsPerToken = parsePositiveInteger(valueAfter(args, ++i, '--chars-per-token'), '--chars-per-token');
82
+ } else if (arg.startsWith('--chars-per-token=')) {
83
+ opts.charsPerToken = parsePositiveInteger(arg.slice('--chars-per-token='.length), '--chars-per-token');
84
+ } else if (arg === '--target') {
85
+ opts.target = parseTarget(valueAfter(args, ++i, '--target'));
86
+ } else if (arg.startsWith('--target=')) {
87
+ opts.target = parseTarget(arg.slice('--target='.length));
88
+ } else if (arg === '--help' || arg === '-h') {
89
+ opts.help = true;
90
+ } else if (arg.startsWith('--')) {
91
+ throw new Error(`unknown flag "${arg}"`);
92
+ } else {
93
+ positional.push(arg);
94
+ }
95
+ }
96
+
97
+ return { positional, opts };
98
+ }
99
+
100
+ function valueAfter(values, index, flag) {
101
+ const value = values[index];
102
+ if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
103
+ return value;
104
+ }
105
+
106
+ function list(opts) {
107
+ const names = listJobForgeContextBundles(PROJECT_DIR);
108
+ if (opts.json) {
109
+ console.log(JSON.stringify(names, null, 2));
110
+ return;
111
+ }
112
+ console.log(names.join('\n'));
113
+ }
114
+
115
+ function explain(positional, opts) {
116
+ const bundle = readBundle(positional);
117
+ if (opts.json) {
118
+ console.log(JSON.stringify(bundle, null, 2));
119
+ return;
120
+ }
121
+ console.log(formatJobForgeContextBundle(bundle));
122
+ }
123
+
124
+ function plan(positional, opts, includeContent) {
125
+ const bundleName = readBundleName(positional);
126
+ const result = planJobForgeContextBundle(bundleName, planOptions(opts, includeContent), PROJECT_DIR);
127
+ if (opts.json) {
128
+ console.log(JSON.stringify(result, null, 2));
129
+ return result;
130
+ }
131
+ console.log(formatJobForgeContextPlan(result));
132
+ return result;
133
+ }
134
+
135
+ function check(positional, opts) {
136
+ const result = plan(positional, opts, false);
137
+ process.exit(result.ok ? 0 : 1);
138
+ }
139
+
140
+ function render(positional, opts) {
141
+ const bundleName = readBundleName(positional);
142
+ const result = planJobForgeContextBundle(bundleName, planOptions(opts, true), PROJECT_DIR);
143
+ const text = renderJobForgeContextPlan(result, opts.target);
144
+ if (opts.json) {
145
+ console.log(JSON.stringify({ target: opts.target, plan: result, text }, null, 2));
146
+ } else {
147
+ console.log(text);
148
+ }
149
+ process.exit(result.ok ? 0 : 1);
150
+ }
151
+
152
+ function readBundle(positional) {
153
+ return resolveJobForgeContextBundle(readBundleName(positional), PROJECT_DIR);
154
+ }
155
+
156
+ function readBundleName(positional) {
157
+ const bundleName = positional[0];
158
+ if (!bundleName) throw new Error('missing bundle name');
159
+ return bundleName;
160
+ }
161
+
162
+ function planOptions(opts, includeContent) {
163
+ return {
164
+ root: opts.root || PROJECT_DIR,
165
+ includeContent,
166
+ tokenBudget: opts.tokenBudget,
167
+ charsPerToken: opts.charsPerToken,
168
+ };
169
+ }
170
+
171
+ function parseTarget(value) {
172
+ if (value === 'markdown' || value === 'json') return value;
173
+ throw new Error('--target must be markdown or json');
174
+ }
175
+
176
+ function parsePositiveInteger(value, flag) {
177
+ const number = Number(value);
178
+ if (!Number.isInteger(number) || number <= 0) throw new Error(`${flag} must be a positive integer`);
179
+ return number;
180
+ }
@@ -10,6 +10,7 @@
10
10
  "npx job-forge verify",
11
11
  "npx job-forge ledger:*",
12
12
  "npx job-forge capabilities:*",
13
+ "npx job-forge context:*",
13
14
  "rg *"
14
15
  ],
15
16
  "deny": [
@@ -0,0 +1,292 @@
1
+ {
2
+ "defaults": {
3
+ "tokenBudget": 12000,
4
+ "charsPerToken": 4
5
+ },
6
+ "bundles": [
7
+ {
8
+ "name": "base",
9
+ "description": "Shared JobForge orchestration contract.",
10
+ "tokenBudget": 4000,
11
+ "files": [
12
+ {
13
+ "path": "AGENTS.harness.md",
14
+ "maxTokens": 3500,
15
+ "required": false,
16
+ "notes": [
17
+ "Consumer-project symlink to the shared harness contract."
18
+ ]
19
+ },
20
+ {
21
+ "path": "iso/instructions.md",
22
+ "maxTokens": 3500,
23
+ "required": false,
24
+ "notes": [
25
+ "Harness-source fallback; mode/reference files should be selected through narrower bundles."
26
+ ]
27
+ }
28
+ ]
29
+ },
30
+ {
31
+ "name": "shared",
32
+ "extends": "base",
33
+ "description": "Shared scoring model plus JobForge orchestration contract.",
34
+ "tokenBudget": 8000,
35
+ "files": [
36
+ {
37
+ "path": "modes/_shared.md",
38
+ "maxTokens": 4500,
39
+ "notes": [
40
+ "Loaded for evaluation-style modes that need archetypes, scoring, and profile references."
41
+ ]
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ "name": "auto-pipeline",
47
+ "extends": "shared",
48
+ "description": "Default JD/URL pipeline context.",
49
+ "tokenBudget": 12000,
50
+ "files": [
51
+ {
52
+ "path": "modes/auto-pipeline.md",
53
+ "maxTokens": 2000
54
+ },
55
+ {
56
+ "path": "modes/reference-setup.md",
57
+ "maxTokens": 2600,
58
+ "required": false,
59
+ "notes": [
60
+ "Load when onboarding, tracker layout, or setup rules are the blocker."
61
+ ]
62
+ }
63
+ ]
64
+ },
65
+ {
66
+ "name": "offer",
67
+ "extends": "shared",
68
+ "description": "Single-offer evaluation context.",
69
+ "tokenBudget": 11000,
70
+ "files": [
71
+ {
72
+ "path": "modes/offer.md",
73
+ "maxTokens": 2400
74
+ },
75
+ {
76
+ "path": "modes/_shared-calibration.md",
77
+ "maxTokens": 2500,
78
+ "required": false,
79
+ "notes": [
80
+ "Load when score calibration or anchor examples are needed."
81
+ ]
82
+ }
83
+ ]
84
+ },
85
+ {
86
+ "name": "compare",
87
+ "extends": "shared",
88
+ "description": "Offer comparison context.",
89
+ "tokenBudget": 8500,
90
+ "files": [
91
+ {
92
+ "path": "modes/compare.md",
93
+ "maxTokens": 600
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ "name": "contact",
99
+ "extends": "shared",
100
+ "description": "LinkedIn outreach context.",
101
+ "tokenBudget": 9000,
102
+ "files": [
103
+ {
104
+ "path": "modes/contact.md",
105
+ "maxTokens": 1200
106
+ }
107
+ ]
108
+ },
109
+ {
110
+ "name": "pdf",
111
+ "extends": "shared",
112
+ "description": "CV/PDF generation context.",
113
+ "tokenBudget": 10000,
114
+ "files": [
115
+ {
116
+ "path": "modes/pdf.md",
117
+ "maxTokens": 2200
118
+ }
119
+ ]
120
+ },
121
+ {
122
+ "name": "apply",
123
+ "extends": "shared",
124
+ "description": "Application form-fill context with Geometra recovery rules.",
125
+ "tokenBudget": 23000,
126
+ "files": [
127
+ {
128
+ "path": "modes/apply.md",
129
+ "maxTokens": 7800
130
+ },
131
+ {
132
+ "path": "modes/reference-geometra.md",
133
+ "maxTokens": 5600
134
+ },
135
+ {
136
+ "path": "modes/reference-portals.md",
137
+ "maxTokens": 2500,
138
+ "required": false,
139
+ "notes": [
140
+ "Load when OTP, proxy, or portal configuration is the blocker."
141
+ ]
142
+ }
143
+ ]
144
+ },
145
+ {
146
+ "name": "pipeline",
147
+ "extends": "shared",
148
+ "description": "Pending URL inbox context.",
149
+ "tokenBudget": 12000,
150
+ "files": [
151
+ {
152
+ "path": "modes/pipeline.md",
153
+ "maxTokens": 1500
154
+ },
155
+ {
156
+ "path": "modes/reference-setup.md",
157
+ "maxTokens": 2600,
158
+ "required": false
159
+ }
160
+ ]
161
+ },
162
+ {
163
+ "name": "scan",
164
+ "extends": "shared",
165
+ "description": "Portal scanning context.",
166
+ "tokenBudget": 18000,
167
+ "files": [
168
+ {
169
+ "path": "modes/scan.md",
170
+ "maxTokens": 6500
171
+ },
172
+ {
173
+ "path": "modes/reference-portals.md",
174
+ "maxTokens": 2500
175
+ }
176
+ ]
177
+ },
178
+ {
179
+ "name": "batch",
180
+ "extends": "shared",
181
+ "description": "Batch processing and durable runner context.",
182
+ "tokenBudget": 16000,
183
+ "files": [
184
+ {
185
+ "path": "modes/batch.md",
186
+ "maxTokens": 1500
187
+ },
188
+ {
189
+ "path": "batch/batch-prompt.md",
190
+ "maxTokens": 4800,
191
+ "required": false,
192
+ "notes": [
193
+ "Needed when invoking or auditing the standalone batch runner prompt."
194
+ ]
195
+ },
196
+ {
197
+ "path": "modes/reference-setup.md",
198
+ "maxTokens": 2600,
199
+ "required": false
200
+ }
201
+ ]
202
+ },
203
+ {
204
+ "name": "tracker",
205
+ "extends": "base",
206
+ "description": "Tracker/status context with setup reference.",
207
+ "tokenBudget": 7000,
208
+ "files": [
209
+ {
210
+ "path": "modes/tracker.md",
211
+ "maxTokens": 500
212
+ },
213
+ {
214
+ "path": "modes/reference-setup.md",
215
+ "maxTokens": 2600
216
+ }
217
+ ]
218
+ },
219
+ {
220
+ "name": "deep",
221
+ "extends": "base",
222
+ "description": "Company research context.",
223
+ "tokenBudget": 5500,
224
+ "files": [
225
+ {
226
+ "path": "modes/deep.md",
227
+ "maxTokens": 1200
228
+ }
229
+ ]
230
+ },
231
+ {
232
+ "name": "training",
233
+ "extends": "base",
234
+ "description": "Course/certification evaluation context.",
235
+ "tokenBudget": 4500,
236
+ "files": [
237
+ {
238
+ "path": "modes/training.md",
239
+ "maxTokens": 400
240
+ }
241
+ ]
242
+ },
243
+ {
244
+ "name": "project",
245
+ "extends": "base",
246
+ "description": "Portfolio project evaluation context.",
247
+ "tokenBudget": 4500,
248
+ "files": [
249
+ {
250
+ "path": "modes/project.md",
251
+ "maxTokens": 400
252
+ }
253
+ ]
254
+ },
255
+ {
256
+ "name": "followup",
257
+ "extends": "base",
258
+ "description": "Follow-up triage context.",
259
+ "tokenBudget": 5500,
260
+ "files": [
261
+ {
262
+ "path": "modes/followup.md",
263
+ "maxTokens": 1000
264
+ }
265
+ ]
266
+ },
267
+ {
268
+ "name": "rejection",
269
+ "extends": "base",
270
+ "description": "Rejection logging and pattern review context.",
271
+ "tokenBudget": 5500,
272
+ "files": [
273
+ {
274
+ "path": "modes/rejection.md",
275
+ "maxTokens": 1100
276
+ }
277
+ ]
278
+ },
279
+ {
280
+ "name": "negotiation",
281
+ "extends": "base",
282
+ "description": "Offer negotiation context.",
283
+ "tokenBudget": 6500,
284
+ "files": [
285
+ {
286
+ "path": "modes/negotiation.md",
287
+ "maxTokens": 1600
288
+ }
289
+ ]
290
+ }
291
+ ]
292
+ }