job-forge 2.14.18 → 2.14.19
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/.cursor/rules/main.mdc +4 -1
- package/.opencode/skills/job-forge.md +4 -0
- package/AGENTS.md +4 -1
- package/CLAUDE.md +4 -1
- package/README.md +5 -4
- package/bin/job-forge.mjs +30 -0
- package/docs/ARCHITECTURE.md +1 -0
- package/docs/CUSTOMIZATION.md +4 -0
- package/docs/MODEL-ROUTING.md +2 -0
- package/docs/SETUP.md +1 -0
- package/iso/commands/job-forge.md +4 -0
- package/iso/instructions.md +4 -1
- package/lib/jobforge-capabilities.mjs +55 -0
- package/package.json +6 -1
- package/scripts/capabilities.mjs +205 -0
- package/templates/capabilities.json +109 -0
package/.cursor/rules/main.mdc
CHANGED
|
@@ -65,12 +65,15 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
65
65
|
- [D9] Treat `templates/contracts.json` as the source of truth for machine-readable artifacts. Prefer `npx job-forge tracker-line ... --write` for tracker additions; if emitting TSV manually, inspect `npx iso-contract explain jobforge.tracker-row --contracts templates/contracts.json` first. `merge` and `verify` enforce the tracker-row contract locally.
|
|
66
66
|
why: deterministic code owns the exact tracker TSV/table shape; repeated prose gets re-tokenized and agents occasionally misremember it
|
|
67
67
|
|
|
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
|
+
why: executable local policy prevents role-permission drift without adding MCP/tool-schema tokens or loading a capability matrix into the shared prefix
|
|
70
|
+
|
|
68
71
|
## Procedure
|
|
69
72
|
|
|
70
73
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
71
74
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
72
75
|
3. Read the active mode file [D3]; decide inline vs delegated work [D1].
|
|
73
|
-
4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2], proxy prompt hygiene [H8].
|
|
76
|
+
4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
|
|
74
77
|
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
75
78
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
76
79
|
7. Cross-check subagent facts against authoritative files [H7].
|
|
@@ -76,6 +76,10 @@ Local workflow ledger (terminal, outside opencode):
|
|
|
76
76
|
Artifact contracts (terminal, outside opencode):
|
|
77
77
|
npx iso-contract explain jobforge.tracker-row --contracts templates/contracts.json
|
|
78
78
|
npx job-forge tracker-line ... --write # renders + validates tracker TSV locally
|
|
79
|
+
|
|
80
|
+
Role capabilities (terminal, outside opencode):
|
|
81
|
+
npx job-forge capabilities:explain general-free
|
|
82
|
+
npx job-forge capabilities:check general-free --tool browser --mcp geometra --filesystem write
|
|
79
83
|
```
|
|
80
84
|
|
|
81
85
|
---
|
package/AGENTS.md
CHANGED
|
@@ -60,12 +60,15 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
60
60
|
- [D9] Treat `templates/contracts.json` as the source of truth for machine-readable artifacts. Prefer `npx job-forge tracker-line ... --write` for tracker additions; if emitting TSV manually, inspect `npx iso-contract explain jobforge.tracker-row --contracts templates/contracts.json` first. `merge` and `verify` enforce the tracker-row contract locally.
|
|
61
61
|
why: deterministic code owns the exact tracker TSV/table shape; repeated prose gets re-tokenized and agents occasionally misremember it
|
|
62
62
|
|
|
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
|
+
why: executable local policy prevents role-permission drift without adding MCP/tool-schema tokens or loading a capability matrix into the shared prefix
|
|
65
|
+
|
|
63
66
|
## Procedure
|
|
64
67
|
|
|
65
68
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
66
69
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
67
70
|
3. Read the active mode file [D3]; decide inline vs delegated work [D1].
|
|
68
|
-
4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2], proxy prompt hygiene [H8].
|
|
71
|
+
4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
|
|
69
72
|
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
70
73
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
71
74
|
7. Cross-check subagent facts against authoritative files [H7].
|
package/CLAUDE.md
CHANGED
|
@@ -60,12 +60,15 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
60
60
|
- [D9] Treat `templates/contracts.json` as the source of truth for machine-readable artifacts. Prefer `npx job-forge tracker-line ... --write` for tracker additions; if emitting TSV manually, inspect `npx iso-contract explain jobforge.tracker-row --contracts templates/contracts.json` first. `merge` and `verify` enforce the tracker-row contract locally.
|
|
61
61
|
why: deterministic code owns the exact tracker TSV/table shape; repeated prose gets re-tokenized and agents occasionally misremember it
|
|
62
62
|
|
|
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
|
+
why: executable local policy prevents role-permission drift without adding MCP/tool-schema tokens or loading a capability matrix into the shared prefix
|
|
65
|
+
|
|
63
66
|
## Procedure
|
|
64
67
|
|
|
65
68
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
66
69
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
67
70
|
3. Read the active mode file [D3]; decide inline vs delegated work [D1].
|
|
68
|
-
4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2], proxy prompt hygiene [H8].
|
|
71
|
+
4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
|
|
69
72
|
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
70
73
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
71
74
|
7. Cross-check subagent facts against authoritative files [H7].
|
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`, and `.jobforge-ledger/events.jsonl` records deterministic duplicate/status events via `@razroo/iso-ledger`.
|
|
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.
|
|
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** | `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`,
|
|
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. |
|
|
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
|
|
165
|
+
├── templates/ # → states.yml, portals.example.yml, cv-template.html, capabilities.json
|
|
166
166
|
├── batch/batch-prompt.md # → batch worker prompt
|
|
167
167
|
├── batch/batch-runner.sh # → parallel orchestrator
|
|
168
168
|
│
|
|
@@ -188,13 +188,14 @@ 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
|
|
191
|
+
├── templates/ # cv-template.html, portals.example.yml, states.yml, capabilities.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/
|
|
195
195
|
│ ├── batch-orchestrator.mjs # iso-orchestrator-backed batch control loop
|
|
196
196
|
│ ├── tracker-line.mjs # iso-contract-backed tracker TSV renderer
|
|
197
197
|
│ ├── ledger.mjs # iso-ledger-backed workflow-state CLI
|
|
198
|
+
│ ├── capabilities.mjs # iso-capabilities-backed role policy CLI
|
|
198
199
|
│ ├── token-usage-report.mjs # opencode cost analyzer
|
|
199
200
|
│ └── release/check-source.mjs # version gate for npm publish
|
|
200
201
|
├── tracker-lib.mjs / merge-tracker.mjs / dedup-tracker.mjs / verify-pipeline.mjs
|
package/bin/job-forge.mjs
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* telemetry:* Summarize JobForge pipeline status from traces + tracker files
|
|
22
22
|
* guard:* Audit JobForge trace policy with iso-guard
|
|
23
23
|
* ledger:* Query local deterministic workflow state via iso-ledger
|
|
24
|
+
* capabilities:* Query role capability policy via iso-capabilities
|
|
24
25
|
* sync Re-run the harness symlink sync (bin/sync.mjs)
|
|
25
26
|
* help, --help Show this message
|
|
26
27
|
*/
|
|
@@ -84,6 +85,14 @@ const ledgerAliases = {
|
|
|
84
85
|
'ledger:path': 'path',
|
|
85
86
|
};
|
|
86
87
|
|
|
88
|
+
const capabilitiesAliases = {
|
|
89
|
+
'capabilities:list': 'list',
|
|
90
|
+
'capabilities:explain': 'explain',
|
|
91
|
+
'capabilities:check': 'check',
|
|
92
|
+
'capabilities:render': 'render',
|
|
93
|
+
'capabilities:path': 'path',
|
|
94
|
+
};
|
|
95
|
+
|
|
87
96
|
const [, , cmd, ...rest] = process.argv;
|
|
88
97
|
|
|
89
98
|
function printHelp() {
|
|
@@ -114,6 +123,10 @@ Commands:
|
|
|
114
123
|
ledger:rebuild Rebuild .jobforge-ledger/events.jsonl from tracker/pipeline files
|
|
115
124
|
ledger:has Check URL or company+role state without loading tracker files
|
|
116
125
|
ledger:verify Validate the local workflow ledger
|
|
126
|
+
capabilities:list List JobForge role capability policies
|
|
127
|
+
capabilities:explain Explain one role capability policy
|
|
128
|
+
capabilities:check Validate requested tool/MCP/command/fs/network access
|
|
129
|
+
capabilities:render Render compact role guidance for an agent harness
|
|
117
130
|
sync Re-create harness symlinks in the current project
|
|
118
131
|
|
|
119
132
|
Deterministic helpers (prefer these over LLM-derived values):
|
|
@@ -143,6 +156,8 @@ Pass --help after a command to see its own flags, e.g.:
|
|
|
143
156
|
job-forge guard:audit
|
|
144
157
|
job-forge guard:explain
|
|
145
158
|
job-forge ledger:has --company "Acme" --role "Staff Engineer" --status Applied
|
|
159
|
+
job-forge capabilities:explain general-free
|
|
160
|
+
job-forge capabilities:check general-free --tool browser --mcp geometra --command "npx job-forge merge" --filesystem write
|
|
146
161
|
|
|
147
162
|
Project directory resolves to $JOB_FORGE_PROJECT or cwd.`);
|
|
148
163
|
}
|
|
@@ -212,6 +227,21 @@ if (cmd === 'ledger' || ledgerAliases[cmd]) {
|
|
|
212
227
|
process.exit(result.status ?? 1);
|
|
213
228
|
}
|
|
214
229
|
|
|
230
|
+
if (cmd === 'capabilities' || capabilitiesAliases[cmd]) {
|
|
231
|
+
const capabilitiesArgs = cmd === 'capabilities'
|
|
232
|
+
? (rest.length === 0 ? ['help'] : rest)
|
|
233
|
+
: [capabilitiesAliases[cmd], ...rest];
|
|
234
|
+
|
|
235
|
+
const scriptPath = join(PKG_ROOT, 'scripts/capabilities.mjs');
|
|
236
|
+
const result = spawnSync(process.execPath, [scriptPath, ...capabilitiesArgs], {
|
|
237
|
+
stdio: 'inherit',
|
|
238
|
+
cwd: PROJECT_DIR,
|
|
239
|
+
env: process.env,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
process.exit(result.status ?? 1);
|
|
243
|
+
}
|
|
244
|
+
|
|
215
245
|
const rel = commands[cmd];
|
|
216
246
|
if (!rel) {
|
|
217
247
|
console.error(`Unknown command: ${cmd}\n`);
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -175,6 +175,7 @@ Create `data/pipeline.md` when you start using the URL inbox (`/job-forge pipeli
|
|
|
175
175
|
- PDFs: `cv-candidate-{company-slug}-{YYYY-MM-DD}.pdf`
|
|
176
176
|
- 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
177
|
- Ledger: `.jobforge-ledger/events.jsonl` (created by `job-forge ledger:rebuild`, `tracker-line --write`, or `merge`; gitignored personal state)
|
|
178
|
+
- Capabilities: `templates/capabilities.json` (role boundary policy inspected with `job-forge capabilities:*`)
|
|
178
179
|
|
|
179
180
|
## Pipeline Integrity
|
|
180
181
|
|
package/docs/CUSTOMIZATION.md
CHANGED
|
@@ -142,6 +142,10 @@ npx job-forge ledger:verify
|
|
|
142
142
|
|
|
143
143
|
Machine-readable artifact shapes live in `templates/contracts.json` and are enforced by `@razroo/iso-contract`. `job-forge tracker-line` renders tracker additions through the `jobforge.tracker-row` contract, `merge` validates pending TSV/table rows before writing tracker files, and `verify` validates existing tracker rows against the same contract. Custom forks can extend `templates/contracts.json`, but keep the tracker status enum aligned with `templates/states.yml`.
|
|
144
144
|
|
|
145
|
+
## JobForge role capabilities
|
|
146
|
+
|
|
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
|
+
|
|
145
149
|
## JobForge guard audits
|
|
146
150
|
|
|
147
151
|
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.
|
package/docs/MODEL-ROUTING.md
CHANGED
|
@@ -53,6 +53,8 @@ The orchestrator can only dispatch to these three agents. Accidental self-calls
|
|
|
53
53
|
```
|
|
54
54
|
Disables ~30 MCP tool schemas globally; each agent re-enables only what it needs in its own `.opencode/agents/<name>.md` frontmatter. Saves ~2-3K input tokens per request in the orchestrator.
|
|
55
55
|
|
|
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
|
+
|
|
56
58
|
**3. Thinking budgets** (`reasoningEffort` in agent frontmatter):
|
|
57
59
|
- `@general-free`: `minimal` — procedural work shouldn't need chain-of-thought
|
|
58
60
|
- `@general-paid`: `medium` — writing quality benefits from thinking
|
package/docs/SETUP.md
CHANGED
|
@@ -126,6 +126,7 @@ From your project root, these commands maintain the tracker and pipeline checks.
|
|
|
126
126
|
| Pipeline health check | `npx job-forge verify` | `npm run verify` |
|
|
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
|
+
| Inspect role capabilities | `npx job-forge capabilities:explain general-free` | `npm run capabilities:explain -- general-free` |
|
|
129
130
|
| Map status column to canonical labels | `npx job-forge normalize` | `npm run normalize` |
|
|
130
131
|
| Merge duplicate company/role rows | `npx job-forge dedup` | `npm run dedup` |
|
|
131
132
|
| Generate ATS-optimized CV PDF | `npx job-forge pdf` | `npm run pdf` |
|
|
@@ -79,6 +79,10 @@ Local workflow ledger (terminal, outside opencode):
|
|
|
79
79
|
Artifact contracts (terminal, outside opencode):
|
|
80
80
|
npx iso-contract explain jobforge.tracker-row --contracts templates/contracts.json
|
|
81
81
|
npx job-forge tracker-line ... --write # renders + validates tracker TSV locally
|
|
82
|
+
|
|
83
|
+
Role capabilities (terminal, outside opencode):
|
|
84
|
+
npx job-forge capabilities:explain general-free
|
|
85
|
+
npx job-forge capabilities:check general-free --tool browser --mcp geometra --filesystem write
|
|
82
86
|
```
|
|
83
87
|
|
|
84
88
|
---
|
package/iso/instructions.md
CHANGED
|
@@ -60,12 +60,15 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
60
60
|
- [D9] Treat `templates/contracts.json` as the source of truth for machine-readable artifacts. Prefer `npx job-forge tracker-line ... --write` for tracker additions; if emitting TSV manually, inspect `npx iso-contract explain jobforge.tracker-row --contracts templates/contracts.json` first. `merge` and `verify` enforce the tracker-row contract locally.
|
|
61
61
|
why: deterministic code owns the exact tracker TSV/table shape; repeated prose gets re-tokenized and agents occasionally misremember it
|
|
62
62
|
|
|
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
|
+
why: executable local policy prevents role-permission drift without adding MCP/tool-schema tokens or loading a capability matrix into the shared prefix
|
|
65
|
+
|
|
63
66
|
## Procedure
|
|
64
67
|
|
|
65
68
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
66
69
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
67
70
|
3. Read the active mode file [D3]; decide inline vs delegated work [D1].
|
|
68
|
-
4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2], proxy prompt hygiene [H8].
|
|
71
|
+
4. Prepare Geometra dispatches: cleanup [H3], ledger prefilter when present [D8], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
|
|
69
72
|
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
70
73
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
71
74
|
7. Cross-check subagent facts against authoritative files [H7].
|
|
@@ -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
|
+
checkRoleCapability,
|
|
6
|
+
formatCheckResult,
|
|
7
|
+
formatResolvedRole,
|
|
8
|
+
loadCapabilityPolicy,
|
|
9
|
+
renderRole,
|
|
10
|
+
resolveRole,
|
|
11
|
+
roleNames,
|
|
12
|
+
} from '@razroo/iso-capabilities';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const PKG_ROOT = resolve(__dirname, '..');
|
|
16
|
+
export const CAPABILITIES_RELATIVE_PATH = 'templates/capabilities.json';
|
|
17
|
+
|
|
18
|
+
export function resolveProjectDir(projectDir = process.env.JOB_FORGE_PROJECT || process.cwd()) {
|
|
19
|
+
return projectDir;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function jobForgeCapabilitiesPath(projectDir = resolveProjectDir()) {
|
|
23
|
+
const projectPath = join(projectDir, CAPABILITIES_RELATIVE_PATH);
|
|
24
|
+
if (existsSync(projectPath)) return projectPath;
|
|
25
|
+
return join(PKG_ROOT, CAPABILITIES_RELATIVE_PATH);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function loadJobForgeCapabilityPolicy(projectDir = resolveProjectDir()) {
|
|
29
|
+
const path = jobForgeCapabilitiesPath(projectDir);
|
|
30
|
+
return loadCapabilityPolicy(JSON.parse(readFileSync(path, 'utf-8')));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function listJobForgeCapabilityRoles(projectDir = resolveProjectDir()) {
|
|
34
|
+
return roleNames(loadJobForgeCapabilityPolicy(projectDir));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveJobForgeCapabilityRole(name, projectDir = resolveProjectDir()) {
|
|
38
|
+
return resolveRole(loadJobForgeCapabilityPolicy(projectDir), name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function checkJobForgeCapability(name, request, projectDir = resolveProjectDir()) {
|
|
42
|
+
return checkRoleCapability(loadJobForgeCapabilityPolicy(projectDir), name, request);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function formatJobForgeCapabilityCheck(result) {
|
|
46
|
+
return formatCheckResult(result);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatJobForgeCapabilityRole(role) {
|
|
50
|
+
return formatResolvedRole(role);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function renderJobForgeCapabilityRole(role, target = 'markdown') {
|
|
54
|
+
return renderRole(role, target);
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-forge",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.19",
|
|
4
4
|
"description": "AI-powered job search pipeline built on opencode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"ledger:verify": "node bin/job-forge.mjs ledger:verify",
|
|
33
33
|
"ledger:has": "node bin/job-forge.mjs ledger:has",
|
|
34
34
|
"ledger:query": "node bin/job-forge.mjs ledger:query",
|
|
35
|
+
"capabilities:list": "node bin/job-forge.mjs capabilities:list",
|
|
36
|
+
"capabilities:explain": "node bin/job-forge.mjs capabilities:explain",
|
|
37
|
+
"capabilities:check": "node bin/job-forge.mjs capabilities:check",
|
|
38
|
+
"capabilities:render": "node bin/job-forge.mjs capabilities:render",
|
|
35
39
|
"plan": "iso plan .",
|
|
36
40
|
"lint:agentmd": "agentmd lint iso/instructions.md",
|
|
37
41
|
"lint:modes": "isolint lint modes/",
|
|
@@ -96,6 +100,7 @@
|
|
|
96
100
|
"node": ">=20.6.0"
|
|
97
101
|
},
|
|
98
102
|
"dependencies": {
|
|
103
|
+
"@razroo/iso-capabilities": "^0.1.0",
|
|
99
104
|
"@razroo/iso-contract": "^0.1.0",
|
|
100
105
|
"@razroo/iso-guard": "^0.1.0",
|
|
101
106
|
"@razroo/iso-ledger": "^0.1.0",
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { PROJECT_DIR } from '../tracker-lib.mjs';
|
|
4
|
+
import {
|
|
5
|
+
checkJobForgeCapability,
|
|
6
|
+
formatJobForgeCapabilityCheck,
|
|
7
|
+
formatJobForgeCapabilityRole,
|
|
8
|
+
jobForgeCapabilitiesPath,
|
|
9
|
+
listJobForgeCapabilityRoles,
|
|
10
|
+
renderJobForgeCapabilityRole,
|
|
11
|
+
resolveJobForgeCapabilityRole,
|
|
12
|
+
} from '../lib/jobforge-capabilities.mjs';
|
|
13
|
+
|
|
14
|
+
const USAGE = `job-forge capabilities - deterministic role capability policy
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
job-forge capabilities:list [--json]
|
|
18
|
+
job-forge capabilities:explain <role> [--json]
|
|
19
|
+
job-forge capabilities:check <role> [--tool <name>] [--mcp <name>] [--command <cmd>] [--filesystem read|write] [--network off|restricted|on] [--json]
|
|
20
|
+
job-forge capabilities:render <role> [--target markdown|claude|codex|cursor|opencode|json] [--json]
|
|
21
|
+
job-forge capabilities:path
|
|
22
|
+
|
|
23
|
+
The policy is templates/capabilities.json. It is local project state, not an
|
|
24
|
+
MCP and not prompt context.`;
|
|
25
|
+
|
|
26
|
+
const [cmd = 'help', ...rawArgs] = process.argv.slice(2);
|
|
27
|
+
const { positional, opts } = parseArgs(rawArgs);
|
|
28
|
+
|
|
29
|
+
if (opts.help || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
30
|
+
console.log(USAGE);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
if (cmd === 'path') {
|
|
36
|
+
console.log(jobForgeCapabilitiesPath(PROJECT_DIR));
|
|
37
|
+
} else if (cmd === 'list') {
|
|
38
|
+
list(opts);
|
|
39
|
+
} else if (cmd === 'explain') {
|
|
40
|
+
explain(positional, opts);
|
|
41
|
+
} else if (cmd === 'check') {
|
|
42
|
+
check(positional, opts);
|
|
43
|
+
} else if (cmd === 'render') {
|
|
44
|
+
render(positional, opts);
|
|
45
|
+
} else {
|
|
46
|
+
console.error(`unknown capabilities command "${cmd}"\n`);
|
|
47
|
+
console.error(USAGE);
|
|
48
|
+
process.exit(2);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseArgs(args) {
|
|
56
|
+
const positional = [];
|
|
57
|
+
const opts = {
|
|
58
|
+
json: false,
|
|
59
|
+
help: false,
|
|
60
|
+
target: 'markdown',
|
|
61
|
+
tools: [],
|
|
62
|
+
mcp: [],
|
|
63
|
+
commands: [],
|
|
64
|
+
filesystem: [],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < args.length; i++) {
|
|
68
|
+
const arg = args[i];
|
|
69
|
+
if (arg === '--json') {
|
|
70
|
+
opts.json = true;
|
|
71
|
+
} else if (arg === '--tool') {
|
|
72
|
+
opts.tools.push(valueAfter(args, ++i, '--tool'));
|
|
73
|
+
} else if (arg.startsWith('--tool=')) {
|
|
74
|
+
opts.tools.push(arg.slice('--tool='.length));
|
|
75
|
+
} else if (arg === '--mcp') {
|
|
76
|
+
opts.mcp.push(valueAfter(args, ++i, '--mcp'));
|
|
77
|
+
} else if (arg.startsWith('--mcp=')) {
|
|
78
|
+
opts.mcp.push(arg.slice('--mcp='.length));
|
|
79
|
+
} else if (arg === '--command') {
|
|
80
|
+
opts.commands.push(valueAfter(args, ++i, '--command'));
|
|
81
|
+
} else if (arg.startsWith('--command=')) {
|
|
82
|
+
opts.commands.push(arg.slice('--command='.length));
|
|
83
|
+
} else if (arg === '--filesystem') {
|
|
84
|
+
opts.filesystem.push(parseFilesystem(valueAfter(args, ++i, '--filesystem')));
|
|
85
|
+
} else if (arg.startsWith('--filesystem=')) {
|
|
86
|
+
opts.filesystem.push(parseFilesystem(arg.slice('--filesystem='.length)));
|
|
87
|
+
} else if (arg === '--network') {
|
|
88
|
+
opts.network = parseNetwork(valueAfter(args, ++i, '--network'));
|
|
89
|
+
} else if (arg.startsWith('--network=')) {
|
|
90
|
+
opts.network = parseNetwork(arg.slice('--network='.length));
|
|
91
|
+
} else if (arg === '--target') {
|
|
92
|
+
opts.target = parseTarget(valueAfter(args, ++i, '--target'));
|
|
93
|
+
} else if (arg.startsWith('--target=')) {
|
|
94
|
+
opts.target = parseTarget(arg.slice('--target='.length));
|
|
95
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
96
|
+
opts.help = true;
|
|
97
|
+
} else if (arg.startsWith('--')) {
|
|
98
|
+
throw new Error(`unknown flag "${arg}"`);
|
|
99
|
+
} else {
|
|
100
|
+
positional.push(arg);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { positional, opts };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function valueAfter(values, index, flag) {
|
|
108
|
+
const value = values[index];
|
|
109
|
+
if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function list(opts) {
|
|
114
|
+
const names = listJobForgeCapabilityRoles(PROJECT_DIR);
|
|
115
|
+
if (opts.json) {
|
|
116
|
+
console.log(JSON.stringify(names, null, 2));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(names.join('\n'));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function explain(positional, opts) {
|
|
123
|
+
const role = readRole(positional);
|
|
124
|
+
if (opts.json) {
|
|
125
|
+
console.log(JSON.stringify(role, null, 2));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
console.log(formatJobForgeCapabilityRole(role));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function check(positional, opts) {
|
|
132
|
+
const roleName = positional[0];
|
|
133
|
+
if (!roleName) throw new Error('missing role name');
|
|
134
|
+
const request = requestFromOptions(opts);
|
|
135
|
+
if (!hasRequest(request)) {
|
|
136
|
+
throw new Error('capabilities:check requires at least one --tool, --mcp, --command, --filesystem, or --network');
|
|
137
|
+
}
|
|
138
|
+
const result = checkJobForgeCapability(roleName, request, PROJECT_DIR);
|
|
139
|
+
if (opts.json) {
|
|
140
|
+
console.log(JSON.stringify(result, null, 2));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(formatJobForgeCapabilityCheck(result));
|
|
143
|
+
}
|
|
144
|
+
process.exit(result.ok ? 0 : 1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function render(positional, opts) {
|
|
148
|
+
const role = readRole(positional);
|
|
149
|
+
const text = renderJobForgeCapabilityRole(role, opts.target);
|
|
150
|
+
if (opts.json) {
|
|
151
|
+
console.log(JSON.stringify({ target: opts.target, role, text }, null, 2));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log(text);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readRole(positional) {
|
|
158
|
+
const roleName = positional[0];
|
|
159
|
+
if (!roleName) throw new Error('missing role name');
|
|
160
|
+
return resolveJobForgeCapabilityRole(roleName, PROJECT_DIR);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function requestFromOptions(opts) {
|
|
164
|
+
return {
|
|
165
|
+
tools: opts.tools.length ? opts.tools : undefined,
|
|
166
|
+
mcp: opts.mcp.length ? opts.mcp : undefined,
|
|
167
|
+
commands: opts.commands.length ? opts.commands : undefined,
|
|
168
|
+
filesystem: opts.filesystem.length ? opts.filesystem : undefined,
|
|
169
|
+
network: opts.network,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function hasRequest(request) {
|
|
174
|
+
return Boolean(
|
|
175
|
+
request.tools?.length ||
|
|
176
|
+
request.mcp?.length ||
|
|
177
|
+
request.commands?.length ||
|
|
178
|
+
request.filesystem?.length ||
|
|
179
|
+
request.network,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseFilesystem(value) {
|
|
184
|
+
if (value === 'read' || value === 'write') return value;
|
|
185
|
+
throw new Error('--filesystem must be read or write');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function parseNetwork(value) {
|
|
189
|
+
if (value === 'off' || value === 'restricted' || value === 'on') return value;
|
|
190
|
+
throw new Error('--network must be off, restricted, or on');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseTarget(value) {
|
|
194
|
+
if (
|
|
195
|
+
value === 'markdown' ||
|
|
196
|
+
value === 'claude' ||
|
|
197
|
+
value === 'codex' ||
|
|
198
|
+
value === 'cursor' ||
|
|
199
|
+
value === 'opencode' ||
|
|
200
|
+
value === 'json'
|
|
201
|
+
) {
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
throw new Error('--target must be markdown, claude, codex, cursor, opencode, or json');
|
|
205
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"roles": [
|
|
3
|
+
{
|
|
4
|
+
"name": "jobforge-base",
|
|
5
|
+
"description": "Safe local JobForge role for reading project state and running deterministic verification commands.",
|
|
6
|
+
"tools": ["read", "search", "shell"],
|
|
7
|
+
"mcp": [],
|
|
8
|
+
"commands": {
|
|
9
|
+
"allow": [
|
|
10
|
+
"npx job-forge verify",
|
|
11
|
+
"npx job-forge ledger:*",
|
|
12
|
+
"npx job-forge capabilities:*",
|
|
13
|
+
"rg *"
|
|
14
|
+
],
|
|
15
|
+
"deny": [
|
|
16
|
+
"rm -rf *"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"filesystem": "read-only",
|
|
20
|
+
"network": "off",
|
|
21
|
+
"notes": [
|
|
22
|
+
"Do not paste secrets from config/profile.yml into prompts, traces, or summaries.",
|
|
23
|
+
"Prefer deterministic JobForge commands over re-reading growing tracker files when a local command can answer the question."
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "orchestrator",
|
|
28
|
+
"description": "Primary session role that routes work, checks policy, and delegates tool-heavy browser/application work.",
|
|
29
|
+
"extends": "jobforge-base",
|
|
30
|
+
"tools": ["task"],
|
|
31
|
+
"commands": {
|
|
32
|
+
"allow": [
|
|
33
|
+
"npx job-forge merge",
|
|
34
|
+
"npx job-forge guard:*",
|
|
35
|
+
"npx job-forge telemetry:*",
|
|
36
|
+
"npx job-forge trace:*"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"filesystem": "read-only",
|
|
40
|
+
"network": "off",
|
|
41
|
+
"notes": [
|
|
42
|
+
"Delegate multi-job Geometra work to subagents; do not run browser-heavy application loops inline.",
|
|
43
|
+
"Use capabilities checks when changing or auditing role boundaries."
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "general-free",
|
|
48
|
+
"description": "Procedural worker for application form fills, TSV writes, merges, verification, OTP retrieval, and portal metadata extraction.",
|
|
49
|
+
"extends": "jobforge-base",
|
|
50
|
+
"tools": ["browser", "write"],
|
|
51
|
+
"mcp": ["geometra", "gmail"],
|
|
52
|
+
"commands": {
|
|
53
|
+
"allow": [
|
|
54
|
+
"npx job-forge tracker-line *",
|
|
55
|
+
"npx job-forge merge",
|
|
56
|
+
"npx job-forge verify",
|
|
57
|
+
"npx job-forge ledger:*",
|
|
58
|
+
"npx job-forge capabilities:*"
|
|
59
|
+
],
|
|
60
|
+
"deny": [
|
|
61
|
+
"task *"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"filesystem": "project-write",
|
|
65
|
+
"network": "restricted",
|
|
66
|
+
"notes": [
|
|
67
|
+
"Use for procedural, tool-heavy work where output shape is validated by JobForge commands.",
|
|
68
|
+
"Do not spawn nested tasks."
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "general-paid",
|
|
73
|
+
"description": "Quality-sensitive writing and evaluation worker for reports, cover letters, interview stories, and outreach.",
|
|
74
|
+
"extends": "jobforge-base",
|
|
75
|
+
"tools": ["write"],
|
|
76
|
+
"commands": {
|
|
77
|
+
"allow": [
|
|
78
|
+
"npx job-forge render-report-header *",
|
|
79
|
+
"npx job-forge pdf",
|
|
80
|
+
"npx job-forge tracker-line *",
|
|
81
|
+
"npx job-forge verify"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
"filesystem": "project-write",
|
|
85
|
+
"network": "restricted",
|
|
86
|
+
"notes": [
|
|
87
|
+
"Use for narrative quality, personalization, and scoring explanations rather than browser automation."
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "glm-minimal",
|
|
92
|
+
"description": "Narrow one-shot extractor/classifier role for small structured transforms.",
|
|
93
|
+
"extends": "jobforge-base",
|
|
94
|
+
"tools": [],
|
|
95
|
+
"commands": {
|
|
96
|
+
"allow": [
|
|
97
|
+
"npx job-forge slugify *",
|
|
98
|
+
"npx job-forge today",
|
|
99
|
+
"npx job-forge next-num"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
"filesystem": "read-only",
|
|
103
|
+
"network": "off",
|
|
104
|
+
"notes": [
|
|
105
|
+
"Keep input small and output structured; no browser, MCP, or nested task work."
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|