job-forge 2.14.28 → 2.14.30
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 +9 -3
- package/.opencode/skills/job-forge.md +15 -5
- package/AGENTS.md +9 -3
- package/CLAUDE.md +9 -3
- package/README.md +7 -4
- package/bin/create-job-forge.mjs +9 -1
- package/bin/job-forge.mjs +58 -0
- package/docs/ARCHITECTURE.md +5 -1
- package/docs/CUSTOMIZATION.md +8 -0
- package/docs/README.md +1 -1
- package/docs/SETUP.md +4 -0
- package/iso/commands/job-forge.md +15 -5
- package/iso/instructions.md +9 -3
- package/lib/jobforge-postflight.mjs +29 -0
- package/lib/jobforge-redact.mjs +25 -0
- package/modes/apply.md +10 -3
- package/package.json +11 -2
- package/scripts/postflight.mjs +151 -0
- package/scripts/redact.mjs +180 -0
- package/templates/migrations.json +12 -1
- package/templates/postflight.json +79 -0
- package/templates/redact.json +77 -0
package/.cursor/rules/main.mdc
CHANGED
|
@@ -86,18 +86,24 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
86
86
|
- [D16] Treat `templates/preflight.json` as the source of truth for multi-apply dispatch safety. After candidate facts and gates are materialized from authoritative files, run `npx job-forge preflight:plan --candidates <file>` or `npx job-forge preflight:check --candidates <file>` before task dispatch; follow the emitted rounds and pre/post steps. This does not replace H2 four-source grep until those facts are materialized into the candidate JSON.
|
|
87
87
|
why: `iso-preflight` is not an MCP and adds no prompt/tool-schema tokens; it turns file-backed facts, duplicate/location gates, max-two rounds, and cleanup/merge/verify steps into an executable local plan instead of repeated prose
|
|
88
88
|
|
|
89
|
+
- [D17] Treat `templates/postflight.json` as the source of truth for multi-apply dispatch settlement. Save the JSON preflight plan and per-round observed dispatch/outcome/artifact records, then run `npx job-forge postflight:status --plan <plan.json> --outcomes <outcomes.json>` after each round and `npx job-forge postflight:check ...` after merge/verify. Follow its next action instead of inferring completion from subagent prose.
|
|
90
|
+
why: `iso-postflight` is not an MCP and adds no prompt/tool-schema tokens; it makes "round complete", missing TSVs, failed candidates, replacements, merge, and verify an executable local gate instead of repeated orchestration prose
|
|
91
|
+
|
|
92
|
+
- [D18] Treat `templates/redact.json` as the source of truth before exporting local traces, prompts, reports, or fixtures outside the project. Use `npx job-forge redact:scan --input <file>`, `redact:apply --input <file> --output .jobforge-redacted/<file>`, or `redact:verify --input <file>` instead of hand-redacting with prose. This complements H8; it does not make it acceptable to paste secrets into prompts.
|
|
93
|
+
why: `iso-redact` is not an MCP and adds no prompt/tool-schema tokens; it gives deterministic safe-export checks whose findings do not print matched secret values
|
|
94
|
+
|
|
89
95
|
## Procedure
|
|
90
96
|
|
|
91
97
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
92
98
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
93
|
-
3. Read the active mode file [D3]. Use context bundle checks when changing context loads [D11]. Check cached artifacts before URL/JD refetches [D12]. Use artifact index lookups before broad file reads when they can answer the question [D13]. Use canonical identity keys for duplicate checks [D15]. Use migration checks for harness drift [D14]. Decide inline vs delegated work [D1].
|
|
99
|
+
3. Read the active mode file [D3]. Use context bundle checks when changing context loads [D11]. Check cached artifacts before URL/JD refetches [D12]. Use artifact index lookups before broad file reads when they can answer the question [D13]. Use canonical identity keys for duplicate checks [D15]. Use migration checks for harness drift [D14]. Use redaction checks before exporting local artifacts [D18]. Decide inline vs delegated work [D1].
|
|
94
100
|
4. Prepare Geometra dispatches: cleanup [H3], canon/index/ledger prefilter when useful [D8, D13, D15], dedupe [H2], location filter [D5], materialize candidate facts/gates and run preflight plan/check [D16], routing [D2, D10], proxy prompt hygiene [H8].
|
|
95
|
-
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
101
|
+
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b], then settle the round with postflight status [D17].
|
|
96
102
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
97
103
|
7. Cross-check subagent facts against authoritative files [H7].
|
|
98
104
|
8. Apply score gate [D4].
|
|
99
105
|
9. Merge contract-validated TSV outcomes [H6, D9].
|
|
100
|
-
10. Verify tracker before ending [H6].
|
|
106
|
+
10. Verify tracker and run postflight check before ending [H6, D17].
|
|
101
107
|
|
|
102
108
|
## Routing
|
|
103
109
|
|
|
@@ -86,6 +86,10 @@ Preflight dispatch plans (terminal, outside opencode):
|
|
|
86
86
|
npx job-forge preflight:plan --candidates batch/preflight-candidates.json
|
|
87
87
|
npx job-forge preflight:check --candidates batch/preflight-candidates.json
|
|
88
88
|
|
|
89
|
+
Postflight dispatch settlement (terminal, outside opencode):
|
|
90
|
+
npx job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
91
|
+
npx job-forge postflight:check --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
92
|
+
|
|
89
93
|
Consumer migrations (terminal, outside opencode):
|
|
90
94
|
npx job-forge migrate:plan # preview package.json/.gitignore drift
|
|
91
95
|
npx job-forge migrate:apply # apply safe harness upgrade migrations
|
|
@@ -205,7 +209,7 @@ Step 4 — Materialize and check the dispatch plan
|
|
|
205
209
|
decision.
|
|
206
210
|
- Run npx job-forge preflight:check --candidates <file> to fail on missing
|
|
207
211
|
sources or blocked gates, then npx job-forge preflight:plan --candidates
|
|
208
|
-
<file> to get the bounded round list.
|
|
212
|
+
<file> --json > batch/preflight-plan.json to get the bounded round list.
|
|
209
213
|
- Follow the emitted rounds. Do not dispatch blocked candidates, and do not
|
|
210
214
|
replace H2's four-source grep with preflight unless those grep results are
|
|
211
215
|
present in the candidate JSON.
|
|
@@ -224,18 +228,24 @@ Step 5 — Loop in rounds of 2 (Hard Limit #1)
|
|
|
224
228
|
# A returned task/session id is only a launch receipt, not completion.
|
|
225
229
|
# Do not create a "check task status" task; inspect tracker files or
|
|
226
230
|
# iso-trace if the user asks for status later.
|
|
227
|
-
# Read their return values, log outcomes
|
|
231
|
+
# Read their return values, log outcomes in batch/postflight-outcomes.json
|
|
232
|
+
# with candidateId, status, and a tracker-tsv artifact path for every
|
|
233
|
+
# terminal outcome.
|
|
234
|
+
npx job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
235
|
+
# Follow the emitted next action before dispatching the next round.
|
|
228
236
|
|
|
229
|
-
Step
|
|
237
|
+
Step 6 — Between rounds: clean sessions again
|
|
230
238
|
- geometra_list_sessions()
|
|
231
239
|
- geometra_disconnect({ closeBrowser: true })
|
|
232
240
|
|
|
233
|
-
Step
|
|
241
|
+
Step 7 — After all rounds: reconcile outcomes (Hard Limit #6)
|
|
234
242
|
- bash: npx job-forge merge # consumes batch/tracker-additions/*.tsv into the day file
|
|
235
243
|
- bash: npx job-forge verify # validates URL/status consistency
|
|
244
|
+
- Add merge/verify step observations to batch/postflight-outcomes.json
|
|
245
|
+
- bash: npx job-forge postflight:check --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
236
246
|
- Review output; if verify-pipeline reports issues, fix them before ending.
|
|
237
247
|
|
|
238
|
-
Step
|
|
248
|
+
Step 8 — Aggregate and report
|
|
239
249
|
- Summarize: applied, skipped, failed
|
|
240
250
|
- Do NOT re-dispatch failed jobs automatically. Report them to the user.
|
|
241
251
|
```
|
package/AGENTS.md
CHANGED
|
@@ -81,18 +81,24 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
81
81
|
- [D16] Treat `templates/preflight.json` as the source of truth for multi-apply dispatch safety. After candidate facts and gates are materialized from authoritative files, run `npx job-forge preflight:plan --candidates <file>` or `npx job-forge preflight:check --candidates <file>` before task dispatch; follow the emitted rounds and pre/post steps. This does not replace H2 four-source grep until those facts are materialized into the candidate JSON.
|
|
82
82
|
why: `iso-preflight` is not an MCP and adds no prompt/tool-schema tokens; it turns file-backed facts, duplicate/location gates, max-two rounds, and cleanup/merge/verify steps into an executable local plan instead of repeated prose
|
|
83
83
|
|
|
84
|
+
- [D17] Treat `templates/postflight.json` as the source of truth for multi-apply dispatch settlement. Save the JSON preflight plan and per-round observed dispatch/outcome/artifact records, then run `npx job-forge postflight:status --plan <plan.json> --outcomes <outcomes.json>` after each round and `npx job-forge postflight:check ...` after merge/verify. Follow its next action instead of inferring completion from subagent prose.
|
|
85
|
+
why: `iso-postflight` is not an MCP and adds no prompt/tool-schema tokens; it makes "round complete", missing TSVs, failed candidates, replacements, merge, and verify an executable local gate instead of repeated orchestration prose
|
|
86
|
+
|
|
87
|
+
- [D18] Treat `templates/redact.json` as the source of truth before exporting local traces, prompts, reports, or fixtures outside the project. Use `npx job-forge redact:scan --input <file>`, `redact:apply --input <file> --output .jobforge-redacted/<file>`, or `redact:verify --input <file>` instead of hand-redacting with prose. This complements H8; it does not make it acceptable to paste secrets into prompts.
|
|
88
|
+
why: `iso-redact` is not an MCP and adds no prompt/tool-schema tokens; it gives deterministic safe-export checks whose findings do not print matched secret values
|
|
89
|
+
|
|
84
90
|
## Procedure
|
|
85
91
|
|
|
86
92
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
87
93
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
88
|
-
3. Read the active mode file [D3]. Use context bundle checks when changing context loads [D11]. Check cached artifacts before URL/JD refetches [D12]. Use artifact index lookups before broad file reads when they can answer the question [D13]. Use canonical identity keys for duplicate checks [D15]. Use migration checks for harness drift [D14]. Decide inline vs delegated work [D1].
|
|
94
|
+
3. Read the active mode file [D3]. Use context bundle checks when changing context loads [D11]. Check cached artifacts before URL/JD refetches [D12]. Use artifact index lookups before broad file reads when they can answer the question [D13]. Use canonical identity keys for duplicate checks [D15]. Use migration checks for harness drift [D14]. Use redaction checks before exporting local artifacts [D18]. Decide inline vs delegated work [D1].
|
|
89
95
|
4. Prepare Geometra dispatches: cleanup [H3], canon/index/ledger prefilter when useful [D8, D13, D15], dedupe [H2], location filter [D5], materialize candidate facts/gates and run preflight plan/check [D16], routing [D2, D10], proxy prompt hygiene [H8].
|
|
90
|
-
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
96
|
+
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b], then settle the round with postflight status [D17].
|
|
91
97
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
92
98
|
7. Cross-check subagent facts against authoritative files [H7].
|
|
93
99
|
8. Apply score gate [D4].
|
|
94
100
|
9. Merge contract-validated TSV outcomes [H6, D9].
|
|
95
|
-
10. Verify tracker before ending [H6].
|
|
101
|
+
10. Verify tracker and run postflight check before ending [H6, D17].
|
|
96
102
|
|
|
97
103
|
## Routing
|
|
98
104
|
|
package/CLAUDE.md
CHANGED
|
@@ -81,18 +81,24 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
81
81
|
- [D16] Treat `templates/preflight.json` as the source of truth for multi-apply dispatch safety. After candidate facts and gates are materialized from authoritative files, run `npx job-forge preflight:plan --candidates <file>` or `npx job-forge preflight:check --candidates <file>` before task dispatch; follow the emitted rounds and pre/post steps. This does not replace H2 four-source grep until those facts are materialized into the candidate JSON.
|
|
82
82
|
why: `iso-preflight` is not an MCP and adds no prompt/tool-schema tokens; it turns file-backed facts, duplicate/location gates, max-two rounds, and cleanup/merge/verify steps into an executable local plan instead of repeated prose
|
|
83
83
|
|
|
84
|
+
- [D17] Treat `templates/postflight.json` as the source of truth for multi-apply dispatch settlement. Save the JSON preflight plan and per-round observed dispatch/outcome/artifact records, then run `npx job-forge postflight:status --plan <plan.json> --outcomes <outcomes.json>` after each round and `npx job-forge postflight:check ...` after merge/verify. Follow its next action instead of inferring completion from subagent prose.
|
|
85
|
+
why: `iso-postflight` is not an MCP and adds no prompt/tool-schema tokens; it makes "round complete", missing TSVs, failed candidates, replacements, merge, and verify an executable local gate instead of repeated orchestration prose
|
|
86
|
+
|
|
87
|
+
- [D18] Treat `templates/redact.json` as the source of truth before exporting local traces, prompts, reports, or fixtures outside the project. Use `npx job-forge redact:scan --input <file>`, `redact:apply --input <file> --output .jobforge-redacted/<file>`, or `redact:verify --input <file>` instead of hand-redacting with prose. This complements H8; it does not make it acceptable to paste secrets into prompts.
|
|
88
|
+
why: `iso-redact` is not an MCP and adds no prompt/tool-schema tokens; it gives deterministic safe-export checks whose findings do not print matched secret values
|
|
89
|
+
|
|
84
90
|
## Procedure
|
|
85
91
|
|
|
86
92
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
87
93
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
88
|
-
3. Read the active mode file [D3]. Use context bundle checks when changing context loads [D11]. Check cached artifacts before URL/JD refetches [D12]. Use artifact index lookups before broad file reads when they can answer the question [D13]. Use canonical identity keys for duplicate checks [D15]. Use migration checks for harness drift [D14]. Decide inline vs delegated work [D1].
|
|
94
|
+
3. Read the active mode file [D3]. Use context bundle checks when changing context loads [D11]. Check cached artifacts before URL/JD refetches [D12]. Use artifact index lookups before broad file reads when they can answer the question [D13]. Use canonical identity keys for duplicate checks [D15]. Use migration checks for harness drift [D14]. Use redaction checks before exporting local artifacts [D18]. Decide inline vs delegated work [D1].
|
|
89
95
|
4. Prepare Geometra dispatches: cleanup [H3], canon/index/ledger prefilter when useful [D8, D13, D15], dedupe [H2], location filter [D5], materialize candidate facts/gates and run preflight plan/check [D16], routing [D2, D10], proxy prompt hygiene [H8].
|
|
90
|
-
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
96
|
+
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b], then settle the round with postflight status [D17].
|
|
91
97
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
92
98
|
7. Cross-check subagent facts against authoritative files [H7].
|
|
93
99
|
8. Apply score gate [D4].
|
|
94
100
|
9. Merge contract-validated TSV outcomes [H6, D9].
|
|
95
|
-
10. Verify tracker before ending [H6].
|
|
101
|
+
10. Verify tracker and run postflight check before ending [H6, D17].
|
|
96
102
|
|
|
97
103
|
## Routing
|
|
98
104
|
|
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/canon.json` defines URL/company/role identity keys via `@razroo/iso-canon`, `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`, `templates/preflight.json` defines safe dispatch rounds/gates via `@razroo/iso-preflight`, `templates/migrations.json` defines safe consumer-project upgrades via `@razroo/iso-migrate`, `.jobforge-ledger/events.jsonl` records duplicate/status events via `@razroo/iso-ledger`, `.jobforge-cache/` stores reusable JD/artifact content via `@razroo/iso-cache`, and `.jobforge-index.json` indexes artifact source pointers via `@razroo/iso-index`. None of these add always-on prompt or tool-schema tokens.
|
|
34
|
+
JobForge also keeps MCP-free local workflow state: `templates/canon.json` defines URL/company/role identity keys via `@razroo/iso-canon`, `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`, `templates/preflight.json` defines safe dispatch rounds/gates via `@razroo/iso-preflight`, `templates/postflight.json` defines safe dispatch settlement via `@razroo/iso-postflight`, `templates/redact.json` defines safe-export redaction rules via `@razroo/iso-redact`, `templates/migrations.json` defines safe consumer-project upgrades via `@razroo/iso-migrate`, `.jobforge-ledger/events.jsonl` records duplicate/status events via `@razroo/iso-ledger`, `.jobforge-cache/` stores reusable JD/artifact content via `@razroo/iso-cache`, and `.jobforge-index.json` indexes artifact source pointers via `@razroo/iso-index`. 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 + Canon + Ledger + Capabilities + Context + Cache + Index + Preflight + Migrate** | `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 canon:*` derives stable URL/company/role identity keys, `job-forge ledger:*` queries append-only workflow state, `job-forge capabilities:*` checks role boundaries, `job-forge context:*` plans mode/reference context bundles, `job-forge cache:*` reuses fetched JD/artifact content, `job-forge index:*` queries compact source pointers, `job-forge preflight:*` plans bounded apply dispatch rounds from file-backed candidate facts, and `job-forge migrate:*` applies safe consumer-project upgrades without MCP/tool-schema overhead. |
|
|
81
|
+
| **Trace + Telemetry + Guard + Contract + Canon + Ledger + Capabilities + Context + Cache + Index + Preflight + Postflight + Redact + Migrate** | `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 canon:*` derives stable URL/company/role identity keys, `job-forge ledger:*` queries append-only workflow state, `job-forge capabilities:*` checks role boundaries, `job-forge context:*` plans mode/reference context bundles, `job-forge cache:*` reuses fetched JD/artifact content, `job-forge index:*` queries compact source pointers, `job-forge preflight:*` plans bounded apply dispatch rounds from file-backed candidate facts, `job-forge postflight:*` settles dispatch outcomes/artifacts/post-steps, `job-forge redact:*` sanitizes local exports, and `job-forge migrate:*` applies safe consumer-project upgrades 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
|
|
@@ -148,6 +148,7 @@ my-search/
|
|
|
148
148
|
├── .jobforge-ledger/ # append-only local workflow events (personal, gitignored)
|
|
149
149
|
├── .jobforge-cache/ # content-addressed local JD/artifact cache (personal, gitignored)
|
|
150
150
|
├── .jobforge-index.json # deterministic artifact lookup index (generated, gitignored)
|
|
151
|
+
├── .jobforge-redacted/ # sanitized local exports (generated, gitignored)
|
|
151
152
|
├── reports/ # generated evaluation reports (personal, gitignored)
|
|
152
153
|
├── batch/{batch-input,batch-state}.tsv, tracker-additions/, logs/ # personal
|
|
153
154
|
├── .jobforge-runs/ # durable batch workflow records (generated)
|
|
@@ -164,7 +165,7 @@ my-search/
|
|
|
164
165
|
├── .opencode/skills/job-forge.md # → skill router
|
|
165
166
|
├── .opencode/agents/ # → @general-free, @general-paid, @glm-minimal
|
|
166
167
|
├── modes/ # → _shared.md + skill modes
|
|
167
|
-
├── templates/ # → states.yml, portals.example.yml, cv-template.html, canon.json, capabilities.json, context.json, index.json, preflight.json, migrations.json
|
|
168
|
+
├── templates/ # → states.yml, portals.example.yml, cv-template.html, canon.json, capabilities.json, context.json, index.json, preflight.json, postflight.json, redact.json, migrations.json
|
|
168
169
|
├── batch/batch-prompt.md # → batch worker prompt
|
|
169
170
|
├── batch/batch-runner.sh # → parallel orchestrator
|
|
170
171
|
│
|
|
@@ -190,7 +191,7 @@ JobForge/
|
|
|
190
191
|
│ ├── sync.mjs # postinstall: creates symlinks in consumer project
|
|
191
192
|
│ └── create-job-forge.mjs # scaffolder
|
|
192
193
|
├── modes/ # _shared.md + 16 skill modes
|
|
193
|
-
├── templates/ # cv-template.html, portals.example.yml, states.yml, canon.json, capabilities.json, context.json, preflight.json, migrations.json
|
|
194
|
+
├── templates/ # cv-template.html, portals.example.yml, states.yml, canon.json, capabilities.json, context.json, preflight.json, postflight.json, redact.json, migrations.json
|
|
194
195
|
├── config/profile.example.yml # template for consumer's profile.yml
|
|
195
196
|
├── batch/{batch-prompt.md,batch-runner.sh} # batch orchestrator
|
|
196
197
|
├── scripts/
|
|
@@ -203,6 +204,8 @@ JobForge/
|
|
|
203
204
|
│ ├── index.mjs # iso-index-backed artifact lookup CLI
|
|
204
205
|
│ ├── canon.mjs # iso-canon-backed identity normalization CLI
|
|
205
206
|
│ ├── preflight.mjs # iso-preflight-backed dispatch planning CLI
|
|
207
|
+
│ ├── postflight.mjs # iso-postflight-backed dispatch settlement CLI
|
|
208
|
+
│ ├── redact.mjs # iso-redact-backed safe-export redaction CLI
|
|
206
209
|
│ ├── migrate.mjs # iso-migrate-backed consumer-project migrations
|
|
207
210
|
│ ├── token-usage-report.mjs # opencode cost analyzer
|
|
208
211
|
│ └── release/check-source.mjs # version gate for npm publish
|
package/bin/create-job-forge.mjs
CHANGED
|
@@ -154,6 +154,9 @@ const consumerPkg = {
|
|
|
154
154
|
'preflight:plan': 'job-forge preflight:plan',
|
|
155
155
|
'preflight:check': 'job-forge preflight:check',
|
|
156
156
|
'preflight:explain': 'job-forge preflight:explain',
|
|
157
|
+
'postflight:status': 'job-forge postflight:status',
|
|
158
|
+
'postflight:check': 'job-forge postflight:check',
|
|
159
|
+
'postflight:explain': 'job-forge postflight:explain',
|
|
157
160
|
'migrate:plan': 'job-forge migrate:plan',
|
|
158
161
|
'migrate:apply': 'job-forge migrate:apply',
|
|
159
162
|
'migrate:check': 'job-forge migrate:check',
|
|
@@ -261,6 +264,7 @@ Before doing any work, remember where things live in *this* project:
|
|
|
261
264
|
| Local artifact index | \`.jobforge-index.json\` | Deterministic file/line lookup; use \`job-forge index:*\` |
|
|
262
265
|
| Identity canonicalization | \`templates/canon.json\` | Stable URL/company/role keys; use \`job-forge canon:*\` |
|
|
263
266
|
| Dispatch preflight policy | \`templates/preflight.json\` | Safe apply rounds/gates; use \`job-forge preflight:*\` |
|
|
267
|
+
| Dispatch postflight policy | \`templates/postflight.json\` | Safe apply settlement; use \`job-forge postflight:*\` |
|
|
264
268
|
| Consumer migrations | \`templates/migrations.json\` | Safe script/gitignore upgrades; use \`job-forge migrate:*\` |
|
|
265
269
|
| Scanner config | \`portals.yml\` (project root) | Company configs |
|
|
266
270
|
| Profile / identity | \`config/profile.yml\` | Candidate name, email, target roles |
|
|
@@ -273,7 +277,7 @@ Before doing any work, remember where things live in *this* project:
|
|
|
273
277
|
| Batch input / state | \`batch/batch-input.tsv\`, \`batch/batch-state.tsv\` | Personal data |
|
|
274
278
|
| Generated reports | \`reports/{###}-{company-slug}-{YYYY-MM-DD}.md\` | Gitignored |
|
|
275
279
|
| Generated PDFs | \`output/\` | Gitignored |
|
|
276
|
-
| Templates | \`templates/\` (symlink) | \`cv-template.html\`, \`portals.example.yml\`, \`states.yml
|
|
280
|
+
| Templates | \`templates/\` (symlink) | \`cv-template.html\`, \`portals.example.yml\`, \`states.yml\`, runtime policies |
|
|
277
281
|
| Harness rules | \`AGENTS.harness.md\` (symlink) | Shared operational guide, loaded via \`opencode.json:instructions\` |
|
|
278
282
|
| Harness source | \`node_modules/job-forge/\` | Read this for harness internals |
|
|
279
283
|
|
|
@@ -358,6 +362,9 @@ reports/
|
|
|
358
362
|
batch/batch-state.tsv
|
|
359
363
|
batch/batch-state.tsv.bak
|
|
360
364
|
batch/batch-input.tsv
|
|
365
|
+
batch/preflight-candidates.json
|
|
366
|
+
batch/preflight-plan.json
|
|
367
|
+
batch/postflight-outcomes.json
|
|
361
368
|
batch/tracker-additions/
|
|
362
369
|
!batch/tracker-additions/.gitkeep
|
|
363
370
|
batch/logs/
|
|
@@ -413,6 +420,7 @@ job-forge ledger:status # local deterministic workflow ledger status
|
|
|
413
420
|
job-forge index:status # local artifact index status
|
|
414
421
|
job-forge canon:key company-role --company "Acme, Inc." --role "Senior SWE"
|
|
415
422
|
job-forge preflight:plan --candidates batch/preflight-candidates.json
|
|
423
|
+
job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
416
424
|
job-forge migrate:check # verify consumer package scripts/gitignore are current
|
|
417
425
|
job-forge pdf cv.md out.pdf
|
|
418
426
|
job-forge tokens --days 1 # per-session opencode token usage
|
package/bin/job-forge.mjs
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
* index:* Query local artifacts via iso-index
|
|
28
28
|
* canon:* Compute deterministic identity keys via iso-canon
|
|
29
29
|
* preflight:* Plan safe dispatch rounds via iso-preflight
|
|
30
|
+
* postflight:* Settle dispatch outcomes via iso-postflight
|
|
31
|
+
* redact:* Sanitize local exports via iso-redact
|
|
30
32
|
* migrate:* Apply deterministic consumer-project migrations via iso-migrate
|
|
31
33
|
* sync Re-run the harness symlink sync (bin/sync.mjs)
|
|
32
34
|
* help, --help Show this message
|
|
@@ -145,6 +147,21 @@ const preflightAliases = {
|
|
|
145
147
|
'preflight:path': 'path',
|
|
146
148
|
};
|
|
147
149
|
|
|
150
|
+
const postflightAliases = {
|
|
151
|
+
'postflight:status': 'status',
|
|
152
|
+
'postflight:check': 'check',
|
|
153
|
+
'postflight:explain': 'explain',
|
|
154
|
+
'postflight:path': 'path',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const redactAliases = {
|
|
158
|
+
'redact:scan': 'scan',
|
|
159
|
+
'redact:verify': 'verify',
|
|
160
|
+
'redact:apply': 'apply',
|
|
161
|
+
'redact:explain': 'explain',
|
|
162
|
+
'redact:path': 'path',
|
|
163
|
+
};
|
|
164
|
+
|
|
148
165
|
const migrateAliases = {
|
|
149
166
|
'migrate:plan': 'plan',
|
|
150
167
|
'migrate:apply': 'apply',
|
|
@@ -209,6 +226,13 @@ Commands:
|
|
|
209
226
|
preflight:plan Build bounded dispatch plan from candidate JSON
|
|
210
227
|
preflight:check Fail if preflight candidates are blocked
|
|
211
228
|
preflight:explain Show the active preflight workflow policy
|
|
229
|
+
postflight:status Reconcile dispatch plan, outcomes, artifacts, and post-steps
|
|
230
|
+
postflight:check Fail unless a dispatched workflow is fully settled
|
|
231
|
+
postflight:explain Show the active postflight workflow policy
|
|
232
|
+
redact:scan Scan local text for sensitive values before export
|
|
233
|
+
redact:verify Fail if local text still contains sensitive values
|
|
234
|
+
redact:apply Write a sanitized copy of local text
|
|
235
|
+
redact:explain Show the active redaction policy
|
|
212
236
|
migrate:plan Preview deterministic consumer-project migrations
|
|
213
237
|
migrate:apply Apply deterministic consumer-project migrations
|
|
214
238
|
migrate:check Fail if migrations are pending
|
|
@@ -255,6 +279,10 @@ Pass --help after a command to see its own flags, e.g.:
|
|
|
255
279
|
job-forge canon:compare company "OpenAI, Inc." "Open AI"
|
|
256
280
|
job-forge preflight:plan --candidates batch/preflight-candidates.json
|
|
257
281
|
job-forge preflight:check --candidates batch/preflight-candidates.json
|
|
282
|
+
job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
283
|
+
job-forge postflight:check --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
284
|
+
job-forge redact:scan --input raw-session.jsonl
|
|
285
|
+
job-forge redact:apply --input raw-session.jsonl --output .jobforge-redacted/session.jsonl
|
|
258
286
|
job-forge migrate:check
|
|
259
287
|
job-forge migrate:apply
|
|
260
288
|
|
|
@@ -416,6 +444,36 @@ if (cmd === 'preflight' || preflightAliases[cmd]) {
|
|
|
416
444
|
process.exit(result.status ?? 1);
|
|
417
445
|
}
|
|
418
446
|
|
|
447
|
+
if (cmd === 'postflight' || postflightAliases[cmd]) {
|
|
448
|
+
const postflightArgs = cmd === 'postflight'
|
|
449
|
+
? (rest.length === 0 ? ['help'] : rest)
|
|
450
|
+
: [postflightAliases[cmd], ...rest];
|
|
451
|
+
|
|
452
|
+
const scriptPath = join(PKG_ROOT, 'scripts/postflight.mjs');
|
|
453
|
+
const result = spawnSync(process.execPath, [scriptPath, ...postflightArgs], {
|
|
454
|
+
stdio: 'inherit',
|
|
455
|
+
cwd: PROJECT_DIR,
|
|
456
|
+
env: process.env,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
process.exit(result.status ?? 1);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (cmd === 'redact' || redactAliases[cmd]) {
|
|
463
|
+
const redactArgs = cmd === 'redact'
|
|
464
|
+
? (rest.length === 0 ? ['help'] : rest)
|
|
465
|
+
: [redactAliases[cmd], ...rest];
|
|
466
|
+
|
|
467
|
+
const scriptPath = join(PKG_ROOT, 'scripts/redact.mjs');
|
|
468
|
+
const result = spawnSync(process.execPath, [scriptPath, ...redactArgs], {
|
|
469
|
+
stdio: 'inherit',
|
|
470
|
+
cwd: PROJECT_DIR,
|
|
471
|
+
env: process.env,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
process.exit(result.status ?? 1);
|
|
475
|
+
}
|
|
476
|
+
|
|
419
477
|
if (cmd === 'migrate' || migrateAliases[cmd]) {
|
|
420
478
|
const migrateArgs = cmd === 'migrate'
|
|
421
479
|
? (rest.length === 0 ? ['help'] : rest)
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -32,7 +32,7 @@ my-search/
|
|
|
32
32
|
├── .opencode/skills/job-forge.md # → skill router
|
|
33
33
|
├── .opencode/agents/ # → @general-free, @general-paid, @glm-minimal
|
|
34
34
|
├── modes/ # → mode files
|
|
35
|
-
├── templates/ # → states.yml, portals.example.yml, cv-template.html, preflight.json
|
|
35
|
+
├── templates/ # → states.yml, portals.example.yml, cv-template.html, preflight.json, postflight.json
|
|
36
36
|
├── batch/batch-prompt.md # → batch worker prompt
|
|
37
37
|
├── batch/batch-runner.sh # → parallel orchestrator
|
|
38
38
|
└── node_modules/job-forge/ # harness, installed from npm
|
|
@@ -167,6 +167,7 @@ templates/states.yml → Canonical status values
|
|
|
167
167
|
templates/canon.json → Canonical URL/company/role identity keys
|
|
168
168
|
templates/context.json → Deterministic mode/reference context bundle policy
|
|
169
169
|
templates/preflight.json → Safe apply dispatch rounds/gates policy
|
|
170
|
+
templates/postflight.json → Safe apply dispatch settlement policy
|
|
170
171
|
templates/migrations.json → Safe consumer-project upgrade policy
|
|
171
172
|
templates/cv-template.html → PDF generation template
|
|
172
173
|
examples/*.md → Fictional layouts only (not read by scripts; see examples/README.md)
|
|
@@ -183,6 +184,7 @@ Create `data/pipeline.md` when you start using the URL inbox (`/job-forge pipeli
|
|
|
183
184
|
- Index: `.jobforge-index.json` (created on demand by `job-forge index:*`; gitignored local lookup state)
|
|
184
185
|
- Canon: `templates/canon.json` (identity rules inspected with `job-forge canon:*`)
|
|
185
186
|
- Preflight: `templates/preflight.json` (dispatch rounds/gates inspected with `job-forge preflight:*`)
|
|
187
|
+
- Postflight: `templates/postflight.json` (dispatch outcomes/artifacts/post-steps inspected with `job-forge postflight:*`)
|
|
186
188
|
- Migrations: `templates/migrations.json` (applied by `job-forge sync` and inspectable with `job-forge migrate:*`)
|
|
187
189
|
- Capabilities: `templates/capabilities.json` (role boundary policy inspected with `job-forge capabilities:*`)
|
|
188
190
|
- Context: `templates/context.json` (mode/reference file bundles inspected with `job-forge context:*`)
|
|
@@ -232,6 +234,8 @@ Scripts maintain data consistency. In a consumer project they're invoked via the
|
|
|
232
234
|
| `scripts/canon.mjs` | `npx job-forge canon:normalize` / `canon:key` / `canon:compare` | Deterministic `@razroo/iso-canon` identity normalization for URLs, companies, roles, and company+role pairs |
|
|
233
235
|
| `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 |
|
|
234
236
|
| `scripts/preflight.mjs` | `npx job-forge preflight:plan` / `preflight:check` / `preflight:explain` | Deterministic `@razroo/iso-preflight` dispatch planning for file-backed candidate facts and gates |
|
|
237
|
+
| `scripts/postflight.mjs` | `npx job-forge postflight:status` / `postflight:check` / `postflight:explain` | Deterministic `@razroo/iso-postflight` settlement for dispatch outcomes, required tracker TSV artifacts, and merge/verify post-steps |
|
|
238
|
+
| `scripts/redact.mjs` | `npx job-forge redact:scan` / `redact:apply` / `redact:verify` | Deterministic `@razroo/iso-redact` safe-export scanning and sanitization for traces, prompts, reports, and fixtures |
|
|
235
239
|
| `scripts/migrate.mjs` | `npx job-forge migrate:plan` / `migrate:apply` / `migrate:check` | Deterministic `@razroo/iso-migrate` consumer-project upgrades for scripts and generated-artifact ignores |
|
|
236
240
|
| `tracker-lib.mjs` | _(library)_ | Shared helpers for reading/writing day-based tracker files — imported by merge/dedup/verify/normalize |
|
|
237
241
|
| `bin/sync.mjs` | `npx job-forge sync` | Creates the harness symlinks in a consumer project and applies safe migrations (also runs as `postinstall`) |
|
package/docs/CUSTOMIZATION.md
CHANGED
|
@@ -162,6 +162,14 @@ URL, company, role, and company+role identity rules live in `templates/canon.jso
|
|
|
162
162
|
|
|
163
163
|
Application dispatch policy lives in `templates/preflight.json` and is planned locally by `@razroo/iso-preflight`. After candidate facts and gate results have been materialized into JSON, use `job-forge preflight:plan --candidates <file>` to get bounded rounds and required pre/post steps, or `job-forge preflight:check --candidates <file>` to fail on missing source facts or blocked gates. This is not an MCP and does not add prompt or tool-schema tokens; it consumes only the candidate JSON you deliberately pass to it.
|
|
164
164
|
|
|
165
|
+
## JobForge postflight settlement
|
|
166
|
+
|
|
167
|
+
Application settlement policy lives in `templates/postflight.json` and is checked locally by `@razroo/iso-postflight`. After each dispatch round, record observed candidate outcomes and tracker TSV artifact paths in `batch/postflight-outcomes.json`, then run `job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json` to get the next safe action. After `merge` and `verify`, add post-step observations and run `job-forge postflight:check ...` to fail unless the workflow is complete. This is not an MCP and does not add prompt or tool-schema tokens.
|
|
168
|
+
|
|
169
|
+
## JobForge redaction policy
|
|
170
|
+
|
|
171
|
+
Safe-export redaction rules live in `templates/redact.json` and are enforced locally by `@razroo/iso-redact`. Use `job-forge redact:scan --input <file>` before sharing traces, prompts, reports, or fixtures outside the project, `job-forge redact:apply --input <file> --output .jobforge-redacted/<file>` to write a sanitized copy, and `job-forge redact:verify --input <file>` to fail if secrets or configured PII remain. Findings never print matched values. This is not an MCP and does not add prompt or tool-schema tokens.
|
|
172
|
+
|
|
165
173
|
## JobForge consumer migrations
|
|
166
174
|
|
|
167
175
|
Consumer-project migrations live in `templates/migrations.json` and are applied locally by `@razroo/iso-migrate`. `job-forge sync` applies safe migrations automatically after refreshing symlinks; use `JOB_FORGE_SKIP_MIGRATIONS=1` to opt out. Use `job-forge migrate:plan`, `job-forge migrate:apply`, and `job-forge migrate:check` to inspect or enforce script/gitignore drift explicitly. This is not an MCP and does not add prompt or tool-schema tokens.
|
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`, `canon`, `context`, `preflight`, `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`, `canon`, `context`, `preflight`, `postflight`, `redact`, `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
|
@@ -134,6 +134,10 @@ From your project root, these commands maintain the tracker and pipeline checks.
|
|
|
134
134
|
| Inspect local artifact index | `npx job-forge index:status` | `npm run index:status` |
|
|
135
135
|
| Plan safe application dispatch rounds | `npx job-forge preflight:plan --candidates batch/preflight-candidates.json` | `npm run preflight:plan -- --candidates ...` |
|
|
136
136
|
| Fail on blocked preflight candidates | `npx job-forge preflight:check --candidates batch/preflight-candidates.json` | `npm run preflight:check -- --candidates ...` |
|
|
137
|
+
| Settle dispatch outcomes after a round | `npx job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json` | `npm run postflight:status -- --plan ... --outcomes ...` |
|
|
138
|
+
| Fail unless dispatch outcomes and post-steps are complete | `npx job-forge postflight:check --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json` | `npm run postflight:check -- --plan ... --outcomes ...` |
|
|
139
|
+
| Sanitize local text before exporting it | `npx job-forge redact:apply --input raw-session.jsonl --output .jobforge-redacted/session.jsonl` | `npm run redact:apply -- --input ... --output ...` |
|
|
140
|
+
| Verify local text is safe to export | `npx job-forge redact:verify --input .jobforge-redacted/session.jsonl` | `npm run redact:verify -- --input ...` |
|
|
137
141
|
| Inspect pending consumer migrations | `npx job-forge migrate:plan` | `npm run migrate:plan` |
|
|
138
142
|
| Map status column to canonical labels | `npx job-forge normalize` | `npm run normalize` |
|
|
139
143
|
| Merge duplicate company/role rows | `npx job-forge dedup` | `npm run dedup` |
|
|
@@ -89,6 +89,10 @@ Preflight dispatch plans (terminal, outside opencode):
|
|
|
89
89
|
npx job-forge preflight:plan --candidates batch/preflight-candidates.json
|
|
90
90
|
npx job-forge preflight:check --candidates batch/preflight-candidates.json
|
|
91
91
|
|
|
92
|
+
Postflight dispatch settlement (terminal, outside opencode):
|
|
93
|
+
npx job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
94
|
+
npx job-forge postflight:check --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
95
|
+
|
|
92
96
|
Consumer migrations (terminal, outside opencode):
|
|
93
97
|
npx job-forge migrate:plan # preview package.json/.gitignore drift
|
|
94
98
|
npx job-forge migrate:apply # apply safe harness upgrade migrations
|
|
@@ -208,7 +212,7 @@ Step 4 — Materialize and check the dispatch plan
|
|
|
208
212
|
decision.
|
|
209
213
|
- Run npx job-forge preflight:check --candidates <file> to fail on missing
|
|
210
214
|
sources or blocked gates, then npx job-forge preflight:plan --candidates
|
|
211
|
-
<file> to get the bounded round list.
|
|
215
|
+
<file> --json > batch/preflight-plan.json to get the bounded round list.
|
|
212
216
|
- Follow the emitted rounds. Do not dispatch blocked candidates, and do not
|
|
213
217
|
replace H2's four-source grep with preflight unless those grep results are
|
|
214
218
|
present in the candidate JSON.
|
|
@@ -227,18 +231,24 @@ Step 5 — Loop in rounds of 2 (Hard Limit #1)
|
|
|
227
231
|
# A returned task/session id is only a launch receipt, not completion.
|
|
228
232
|
# Do not create a "check task status" task; inspect tracker files or
|
|
229
233
|
# iso-trace if the user asks for status later.
|
|
230
|
-
# Read their return values, log outcomes
|
|
234
|
+
# Read their return values, log outcomes in batch/postflight-outcomes.json
|
|
235
|
+
# with candidateId, status, and a tracker-tsv artifact path for every
|
|
236
|
+
# terminal outcome.
|
|
237
|
+
npx job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
238
|
+
# Follow the emitted next action before dispatching the next round.
|
|
231
239
|
|
|
232
|
-
Step
|
|
240
|
+
Step 6 — Between rounds: clean sessions again
|
|
233
241
|
- geometra_list_sessions()
|
|
234
242
|
- geometra_disconnect({ closeBrowser: true })
|
|
235
243
|
|
|
236
|
-
Step
|
|
244
|
+
Step 7 — After all rounds: reconcile outcomes (Hard Limit #6)
|
|
237
245
|
- bash: npx job-forge merge # consumes batch/tracker-additions/*.tsv into the day file
|
|
238
246
|
- bash: npx job-forge verify # validates URL/status consistency
|
|
247
|
+
- Add merge/verify step observations to batch/postflight-outcomes.json
|
|
248
|
+
- bash: npx job-forge postflight:check --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
239
249
|
- Review output; if verify-pipeline reports issues, fix them before ending.
|
|
240
250
|
|
|
241
|
-
Step
|
|
251
|
+
Step 8 — Aggregate and report
|
|
242
252
|
- Summarize: applied, skipped, failed
|
|
243
253
|
- Do NOT re-dispatch failed jobs automatically. Report them to the user.
|
|
244
254
|
```
|
package/iso/instructions.md
CHANGED
|
@@ -81,18 +81,24 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
81
81
|
- [D16] Treat `templates/preflight.json` as the source of truth for multi-apply dispatch safety. After candidate facts and gates are materialized from authoritative files, run `npx job-forge preflight:plan --candidates <file>` or `npx job-forge preflight:check --candidates <file>` before task dispatch; follow the emitted rounds and pre/post steps. This does not replace H2 four-source grep until those facts are materialized into the candidate JSON.
|
|
82
82
|
why: `iso-preflight` is not an MCP and adds no prompt/tool-schema tokens; it turns file-backed facts, duplicate/location gates, max-two rounds, and cleanup/merge/verify steps into an executable local plan instead of repeated prose
|
|
83
83
|
|
|
84
|
+
- [D17] Treat `templates/postflight.json` as the source of truth for multi-apply dispatch settlement. Save the JSON preflight plan and per-round observed dispatch/outcome/artifact records, then run `npx job-forge postflight:status --plan <plan.json> --outcomes <outcomes.json>` after each round and `npx job-forge postflight:check ...` after merge/verify. Follow its next action instead of inferring completion from subagent prose.
|
|
85
|
+
why: `iso-postflight` is not an MCP and adds no prompt/tool-schema tokens; it makes "round complete", missing TSVs, failed candidates, replacements, merge, and verify an executable local gate instead of repeated orchestration prose
|
|
86
|
+
|
|
87
|
+
- [D18] Treat `templates/redact.json` as the source of truth before exporting local traces, prompts, reports, or fixtures outside the project. Use `npx job-forge redact:scan --input <file>`, `redact:apply --input <file> --output .jobforge-redacted/<file>`, or `redact:verify --input <file>` instead of hand-redacting with prose. This complements H8; it does not make it acceptable to paste secrets into prompts.
|
|
88
|
+
why: `iso-redact` is not an MCP and adds no prompt/tool-schema tokens; it gives deterministic safe-export checks whose findings do not print matched secret values
|
|
89
|
+
|
|
84
90
|
## Procedure
|
|
85
91
|
|
|
86
92
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
87
93
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
88
|
-
3. Read the active mode file [D3]. Use context bundle checks when changing context loads [D11]. Check cached artifacts before URL/JD refetches [D12]. Use artifact index lookups before broad file reads when they can answer the question [D13]. Use canonical identity keys for duplicate checks [D15]. Use migration checks for harness drift [D14]. Decide inline vs delegated work [D1].
|
|
94
|
+
3. Read the active mode file [D3]. Use context bundle checks when changing context loads [D11]. Check cached artifacts before URL/JD refetches [D12]. Use artifact index lookups before broad file reads when they can answer the question [D13]. Use canonical identity keys for duplicate checks [D15]. Use migration checks for harness drift [D14]. Use redaction checks before exporting local artifacts [D18]. Decide inline vs delegated work [D1].
|
|
89
95
|
4. Prepare Geometra dispatches: cleanup [H3], canon/index/ledger prefilter when useful [D8, D13, D15], dedupe [H2], location filter [D5], materialize candidate facts/gates and run preflight plan/check [D16], routing [D2, D10], proxy prompt hygiene [H8].
|
|
90
|
-
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
96
|
+
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b], then settle the round with postflight status [D17].
|
|
91
97
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
92
98
|
7. Cross-check subagent facts against authoritative files [H7].
|
|
93
99
|
8. Apply score gate [D4].
|
|
94
100
|
9. Merge contract-validated TSV outcomes [H6, D9].
|
|
95
|
-
10. Verify tracker before ending [H6].
|
|
101
|
+
10. Verify tracker and run postflight check before ending [H6, D17].
|
|
96
102
|
|
|
97
103
|
## Routing
|
|
98
104
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import {
|
|
4
|
+
loadPostflightConfig,
|
|
5
|
+
parseJson,
|
|
6
|
+
settlePostflight,
|
|
7
|
+
} from '@razroo/iso-postflight';
|
|
8
|
+
|
|
9
|
+
export const POSTFLIGHT_CONFIG_FILE = 'templates/postflight.json';
|
|
10
|
+
export const POSTFLIGHT_WORKFLOW = 'jobforge.apply';
|
|
11
|
+
|
|
12
|
+
export function resolveProjectDir(projectDir = process.env.JOB_FORGE_PROJECT || process.cwd()) {
|
|
13
|
+
return projectDir;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function jobForgePostflightConfigPath(projectDir = resolveProjectDir()) {
|
|
17
|
+
return process.env.JOB_FORGE_POSTFLIGHT_CONFIG || join(projectDir, POSTFLIGHT_CONFIG_FILE);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readJobForgePostflightConfig(projectDir = resolveProjectDir()) {
|
|
21
|
+
const path = jobForgePostflightConfigPath(projectDir);
|
|
22
|
+
return loadPostflightConfig(parseJson(readFileSync(path, 'utf8'), path));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function settleJobForgePostflight(planInput, observationsInput, options = {}, projectDir = resolveProjectDir()) {
|
|
26
|
+
return settlePostflight(readJobForgePostflightConfig(projectDir), planInput, observationsInput, {
|
|
27
|
+
workflow: options.workflow || POSTFLIGHT_WORKFLOW,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import {
|
|
4
|
+
loadRedactConfig,
|
|
5
|
+
parseJson,
|
|
6
|
+
} from '@razroo/iso-redact';
|
|
7
|
+
|
|
8
|
+
export const REDACT_CONFIG_FILE = 'templates/redact.json';
|
|
9
|
+
|
|
10
|
+
export function resolveProjectDir(projectDir = process.env.JOB_FORGE_PROJECT || process.cwd()) {
|
|
11
|
+
return projectDir;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function jobForgeRedactConfigPath(projectDir = resolveProjectDir()) {
|
|
15
|
+
return process.env.JOB_FORGE_REDACT_CONFIG || join(projectDir, REDACT_CONFIG_FILE);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function redactConfigExists(projectDir = resolveProjectDir()) {
|
|
19
|
+
return existsSync(jobForgeRedactConfigPath(projectDir));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function readJobForgeRedactConfig(projectDir = resolveProjectDir()) {
|
|
23
|
+
const path = jobForgeRedactConfigPath(projectDir);
|
|
24
|
+
return loadRedactConfig(parseJson(readFileSync(path, 'utf8'), path));
|
|
25
|
+
}
|
package/modes/apply.md
CHANGED
|
@@ -188,11 +188,18 @@ Step 4 — For round in ceil(N/2):
|
|
|
188
188
|
task(apply to pair[1]) # only if pair has 2
|
|
189
189
|
# WAIT for both final outcomes. A session id is not completion.
|
|
190
190
|
# Do not dispatch round N+1 while round N is still in flight.
|
|
191
|
-
Step 5 —
|
|
192
|
-
|
|
191
|
+
Step 5 — After each round:
|
|
192
|
+
write/update batch/postflight-outcomes.json with candidateId, status,
|
|
193
|
+
and tracker-tsv artifact path for every terminal outcome.
|
|
194
|
+
bash: npx job-forge postflight:status --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
195
|
+
follow the emitted next action before the next dispatch.
|
|
196
|
+
Step 6 — Between rounds: geometra_list_sessions() + geometra_disconnect({closeBrowser: true})
|
|
197
|
+
Step 7 — Reconcile outcomes (Hard Limit #6):
|
|
193
198
|
bash: npx job-forge merge # TSVs → day file
|
|
194
199
|
bash: npx job-forge verify # validate
|
|
195
|
-
|
|
200
|
+
add merge/verify step observations to batch/postflight-outcomes.json
|
|
201
|
+
bash: npx job-forge postflight:check --plan batch/preflight-plan.json --outcomes batch/postflight-outcomes.json
|
|
202
|
+
Step 8 — Summarize outcomes; do NOT auto-retry failures.
|
|
196
203
|
```
|
|
197
204
|
|
|
198
205
|
If a subagent fails, report it in the summary and let the user decide whether to retry. Never auto-retry — re-running a submit step risks duplicate applications. If a subagent returns SKIP because it discovered a duplicate, treat that as a missed preflight check: finish the current round, then choose a replacement candidate only after re-running dedupe against all four sources.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-forge",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.30",
|
|
4
4
|
"description": "AI-powered job search pipeline built on opencode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -62,6 +62,13 @@
|
|
|
62
62
|
"preflight:plan": "node bin/job-forge.mjs preflight:plan",
|
|
63
63
|
"preflight:check": "node bin/job-forge.mjs preflight:check",
|
|
64
64
|
"preflight:explain": "node bin/job-forge.mjs preflight:explain",
|
|
65
|
+
"postflight:status": "node bin/job-forge.mjs postflight:status",
|
|
66
|
+
"postflight:check": "node bin/job-forge.mjs postflight:check",
|
|
67
|
+
"postflight:explain": "node bin/job-forge.mjs postflight:explain",
|
|
68
|
+
"redact:scan": "node bin/job-forge.mjs redact:scan",
|
|
69
|
+
"redact:verify": "node bin/job-forge.mjs redact:verify",
|
|
70
|
+
"redact:apply": "node bin/job-forge.mjs redact:apply",
|
|
71
|
+
"redact:explain": "node bin/job-forge.mjs redact:explain",
|
|
65
72
|
"migrate:plan": "node bin/job-forge.mjs migrate:plan",
|
|
66
73
|
"migrate:apply": "node bin/job-forge.mjs migrate:apply",
|
|
67
74
|
"migrate:check": "node bin/job-forge.mjs migrate:check",
|
|
@@ -132,9 +139,9 @@
|
|
|
132
139
|
"node": ">=20.6.0"
|
|
133
140
|
},
|
|
134
141
|
"dependencies": {
|
|
135
|
-
"@razroo/iso-capabilities": "^0.1.0",
|
|
136
142
|
"@razroo/iso-cache": "^0.1.0",
|
|
137
143
|
"@razroo/iso-canon": "^0.1.0",
|
|
144
|
+
"@razroo/iso-capabilities": "^0.1.0",
|
|
138
145
|
"@razroo/iso-context": "^0.1.0",
|
|
139
146
|
"@razroo/iso-contract": "^0.1.0",
|
|
140
147
|
"@razroo/iso-guard": "^0.1.0",
|
|
@@ -142,7 +149,9 @@
|
|
|
142
149
|
"@razroo/iso-ledger": "^0.1.0",
|
|
143
150
|
"@razroo/iso-migrate": "^0.1.0",
|
|
144
151
|
"@razroo/iso-orchestrator": "^0.1.0",
|
|
152
|
+
"@razroo/iso-postflight": "^0.1.0",
|
|
145
153
|
"@razroo/iso-preflight": "^0.1.0",
|
|
154
|
+
"@razroo/iso-redact": "^0.1.0",
|
|
146
155
|
"@razroo/iso-trace": "^0.4.0",
|
|
147
156
|
"playwright": "^1.58.1"
|
|
148
157
|
},
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { isAbsolute, relative, resolve } from 'path';
|
|
5
|
+
import {
|
|
6
|
+
formatConfigSummary,
|
|
7
|
+
formatPostflightResult,
|
|
8
|
+
loadPostflightConfig,
|
|
9
|
+
parseJson,
|
|
10
|
+
settlePostflight,
|
|
11
|
+
} from '@razroo/iso-postflight';
|
|
12
|
+
import { PROJECT_DIR } from '../tracker-lib.mjs';
|
|
13
|
+
import {
|
|
14
|
+
jobForgePostflightConfigPath,
|
|
15
|
+
readJobForgePostflightConfig,
|
|
16
|
+
settleJobForgePostflight,
|
|
17
|
+
} from '../lib/jobforge-postflight.mjs';
|
|
18
|
+
|
|
19
|
+
const USAGE = `job-forge postflight - deterministic dispatch settlement for JobForge
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
job-forge postflight:status --plan <file> --outcomes <file> [--workflow jobforge.apply] [--json]
|
|
23
|
+
job-forge postflight:check --plan <file> --outcomes <file> [--workflow jobforge.apply] [--json]
|
|
24
|
+
job-forge postflight:explain [--json]
|
|
25
|
+
job-forge postflight:path
|
|
26
|
+
|
|
27
|
+
Plan files are JSON objects with rounds, such as the JSON output from
|
|
28
|
+
job-forge preflight:plan. Outcome files are JSON objects with dispatches,
|
|
29
|
+
outcomes, and post-step observations. The policy is templates/postflight.json.
|
|
30
|
+
This is local project state, not an MCP and not prompt context.`;
|
|
31
|
+
|
|
32
|
+
const [cmd = 'help', ...rawArgs] = process.argv.slice(2);
|
|
33
|
+
const opts = parseArgs(rawArgs);
|
|
34
|
+
|
|
35
|
+
if (opts.help || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
36
|
+
console.log(USAGE);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (cmd === 'path') {
|
|
42
|
+
console.log(configPath(opts));
|
|
43
|
+
} else if (cmd === 'status' || cmd === 'check') {
|
|
44
|
+
runSettlement(cmd, opts);
|
|
45
|
+
} else if (cmd === 'explain') {
|
|
46
|
+
explain(opts);
|
|
47
|
+
} else {
|
|
48
|
+
console.error(`unknown postflight command "${cmd}"\n`);
|
|
49
|
+
console.error(USAGE);
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseArgs(args) {
|
|
58
|
+
const opts = {
|
|
59
|
+
json: false,
|
|
60
|
+
help: false,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < args.length; i++) {
|
|
64
|
+
const arg = args[i];
|
|
65
|
+
if (arg === '--json') {
|
|
66
|
+
opts.json = true;
|
|
67
|
+
} else if (arg === '--plan' || arg === '-p') {
|
|
68
|
+
opts.plan = valueAfter(args, ++i, arg);
|
|
69
|
+
} else if (arg.startsWith('--plan=')) {
|
|
70
|
+
opts.plan = arg.slice('--plan='.length);
|
|
71
|
+
} else if (arg === '--outcomes' || arg === '--observations' || arg === '-o') {
|
|
72
|
+
opts.outcomes = valueAfter(args, ++i, arg);
|
|
73
|
+
} else if (arg.startsWith('--outcomes=')) {
|
|
74
|
+
opts.outcomes = arg.slice('--outcomes='.length);
|
|
75
|
+
} else if (arg.startsWith('--observations=')) {
|
|
76
|
+
opts.outcomes = arg.slice('--observations='.length);
|
|
77
|
+
} else if (arg === '--workflow') {
|
|
78
|
+
opts.workflow = valueAfter(args, ++i, '--workflow');
|
|
79
|
+
} else if (arg.startsWith('--workflow=')) {
|
|
80
|
+
opts.workflow = arg.slice('--workflow='.length);
|
|
81
|
+
} else if (arg === '--config') {
|
|
82
|
+
opts.config = valueAfter(args, ++i, '--config');
|
|
83
|
+
} else if (arg.startsWith('--config=')) {
|
|
84
|
+
opts.config = arg.slice('--config='.length);
|
|
85
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
86
|
+
opts.help = true;
|
|
87
|
+
} else {
|
|
88
|
+
throw new Error(`unknown flag "${arg}"`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return opts;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function runSettlement(mode, opts) {
|
|
96
|
+
if (!opts.plan) throw new Error(`${mode} requires --plan <file>`);
|
|
97
|
+
if (!opts.outcomes) throw new Error(`${mode} requires --outcomes <file>`);
|
|
98
|
+
const plan = readJsonFile(resolveInputPath(opts.plan));
|
|
99
|
+
const observations = readJsonFile(resolveInputPath(opts.outcomes));
|
|
100
|
+
const result = opts.config
|
|
101
|
+
? settlePostflight(readConfig(opts), plan, observations, { workflow: opts.workflow })
|
|
102
|
+
: settleJobForgePostflight(plan, observations, { workflow: opts.workflow }, PROJECT_DIR);
|
|
103
|
+
|
|
104
|
+
if (opts.json) {
|
|
105
|
+
console.log(JSON.stringify(result, null, 2));
|
|
106
|
+
} else {
|
|
107
|
+
console.log(formatPostflightResult(result, mode));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (mode === 'check' && !result.ok) process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function explain(opts) {
|
|
114
|
+
const config = readConfig(opts);
|
|
115
|
+
if (opts.json) {
|
|
116
|
+
console.log(JSON.stringify(config, null, 2));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(`config: ${relativePath(configPath(opts))}`);
|
|
120
|
+
console.log(formatConfigSummary(config));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readConfig(opts) {
|
|
124
|
+
if (opts.config) {
|
|
125
|
+
const path = resolveInputPath(opts.config);
|
|
126
|
+
return loadPostflightConfig(readJsonFile(path));
|
|
127
|
+
}
|
|
128
|
+
return readJobForgePostflightConfig(PROJECT_DIR);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function configPath(opts) {
|
|
132
|
+
return opts.config ? resolveInputPath(opts.config) : jobForgePostflightConfigPath(PROJECT_DIR);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readJsonFile(path) {
|
|
136
|
+
return parseJson(readFileSync(path, 'utf8'), path);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function valueAfter(values, index, flag) {
|
|
140
|
+
const value = values[index];
|
|
141
|
+
if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveInputPath(path) {
|
|
146
|
+
return isAbsolute(path) ? path : resolve(PROJECT_DIR, path);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function relativePath(path) {
|
|
150
|
+
return relative(PROJECT_DIR, path) || '.';
|
|
151
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve } from 'path';
|
|
5
|
+
import {
|
|
6
|
+
formatConfigSummary,
|
|
7
|
+
formatScanResult,
|
|
8
|
+
loadRedactConfig,
|
|
9
|
+
parseJson,
|
|
10
|
+
redactText,
|
|
11
|
+
scanSources,
|
|
12
|
+
} from '@razroo/iso-redact';
|
|
13
|
+
import { PROJECT_DIR } from '../tracker-lib.mjs';
|
|
14
|
+
import {
|
|
15
|
+
jobForgeRedactConfigPath,
|
|
16
|
+
readJobForgeRedactConfig,
|
|
17
|
+
} from '../lib/jobforge-redact.mjs';
|
|
18
|
+
|
|
19
|
+
const USAGE = `job-forge redact - deterministic local redaction for exports
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
job-forge redact:scan [--input <file> ...] [--stdin] [--config <file>] [--json]
|
|
23
|
+
job-forge redact:verify [--input <file> ...] [--stdin] [--config <file>] [--json]
|
|
24
|
+
job-forge redact:apply (--input <file> | --stdin) [--output <file>] [--config <file>] [--json]
|
|
25
|
+
job-forge redact:explain [--config <file>] [--json]
|
|
26
|
+
job-forge redact:path
|
|
27
|
+
|
|
28
|
+
Default policy is templates/redact.json. Findings never print matched values;
|
|
29
|
+
previews are redacted length markers. Use apply to write a sanitized copy
|
|
30
|
+
before exporting traces, prompts, reports, or fixture text.`;
|
|
31
|
+
|
|
32
|
+
const [cmd = 'help', ...rawArgs] = process.argv.slice(2);
|
|
33
|
+
const opts = parseArgs(rawArgs);
|
|
34
|
+
|
|
35
|
+
if (opts.help || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
36
|
+
console.log(USAGE);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (cmd === 'path') {
|
|
42
|
+
console.log(configPath(opts));
|
|
43
|
+
} else if (cmd === 'scan' || cmd === 'verify') {
|
|
44
|
+
scan(cmd, opts);
|
|
45
|
+
} else if (cmd === 'apply') {
|
|
46
|
+
apply(opts);
|
|
47
|
+
} else if (cmd === 'explain') {
|
|
48
|
+
explain(opts);
|
|
49
|
+
} else {
|
|
50
|
+
console.error(`unknown redact command "${cmd}"\n`);
|
|
51
|
+
console.error(USAGE);
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseArgs(args) {
|
|
60
|
+
const opts = {
|
|
61
|
+
inputs: [],
|
|
62
|
+
stdin: false,
|
|
63
|
+
json: false,
|
|
64
|
+
help: false,
|
|
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 === '--input' || arg === '-i') {
|
|
72
|
+
opts.inputs.push(valueAfter(args, ++i, arg));
|
|
73
|
+
} else if (arg.startsWith('--input=')) {
|
|
74
|
+
opts.inputs.push(arg.slice('--input='.length));
|
|
75
|
+
} else if (arg === '--stdin') {
|
|
76
|
+
opts.stdin = true;
|
|
77
|
+
} else if (arg === '--output' || arg === '-o') {
|
|
78
|
+
opts.output = valueAfter(args, ++i, arg);
|
|
79
|
+
} else if (arg.startsWith('--output=')) {
|
|
80
|
+
opts.output = arg.slice('--output='.length);
|
|
81
|
+
} else if (arg === '--config' || arg === '-c') {
|
|
82
|
+
opts.config = valueAfter(args, ++i, arg);
|
|
83
|
+
} else if (arg.startsWith('--config=')) {
|
|
84
|
+
opts.config = arg.slice('--config='.length);
|
|
85
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
86
|
+
opts.help = true;
|
|
87
|
+
} else if (arg.startsWith('--')) {
|
|
88
|
+
throw new Error(`unknown flag "${arg}"`);
|
|
89
|
+
} else {
|
|
90
|
+
opts.inputs.push(arg);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return opts;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function valueAfter(values, index, flag) {
|
|
98
|
+
const value = values[index];
|
|
99
|
+
if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function scan(mode, opts) {
|
|
104
|
+
const sources = readSources(opts);
|
|
105
|
+
if (sources.length === 0) throw new Error(`${mode} requires at least one --input or --stdin source`);
|
|
106
|
+
const result = scanSources(readConfig(opts), sources);
|
|
107
|
+
if (opts.json) {
|
|
108
|
+
console.log(JSON.stringify(result, null, 2));
|
|
109
|
+
} else {
|
|
110
|
+
console.log(formatScanResult(result, mode));
|
|
111
|
+
}
|
|
112
|
+
if (mode === 'verify' && !result.ok) process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function apply(opts) {
|
|
116
|
+
const sources = readSources(opts);
|
|
117
|
+
if (sources.length !== 1) throw new Error('apply requires exactly one --input or --stdin source');
|
|
118
|
+
const source = sources[0];
|
|
119
|
+
const result = redactText(readConfig(opts), source.text, { source: source.name });
|
|
120
|
+
if (opts.output) {
|
|
121
|
+
const output = resolveOutputPath(opts.output);
|
|
122
|
+
mkdirSync(dirname(output), { recursive: true });
|
|
123
|
+
writeFileSync(output, result.text, 'utf8');
|
|
124
|
+
if (opts.json) {
|
|
125
|
+
console.log(JSON.stringify({ ...result, text: undefined, output }, null, 2));
|
|
126
|
+
} else {
|
|
127
|
+
console.log(`iso-redact: wrote ${relativePath(output)} (${result.findings.length} finding(s) redacted)`);
|
|
128
|
+
}
|
|
129
|
+
} else if (opts.json) {
|
|
130
|
+
console.log(JSON.stringify(result, null, 2));
|
|
131
|
+
} else {
|
|
132
|
+
process.stdout.write(result.text);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function explain(opts) {
|
|
137
|
+
const config = readConfig(opts);
|
|
138
|
+
if (opts.json) {
|
|
139
|
+
console.log(JSON.stringify(config, null, 2));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
console.log(`config: ${relativePath(configPath(opts))}`);
|
|
143
|
+
console.log(formatConfigSummary(config));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function readSources(opts) {
|
|
147
|
+
const sources = opts.inputs.map((input) => {
|
|
148
|
+
const path = resolveInputPath(input);
|
|
149
|
+
return {
|
|
150
|
+
name: relativePath(path),
|
|
151
|
+
text: readFileSync(path, 'utf8'),
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
if (opts.stdin) sources.push({ name: '<stdin>', text: readFileSync(0, 'utf8') });
|
|
155
|
+
return sources;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function readConfig(opts) {
|
|
159
|
+
if (opts.config) {
|
|
160
|
+
const path = resolveInputPath(opts.config);
|
|
161
|
+
return loadRedactConfig(parseJson(readFileSync(path, 'utf8'), path));
|
|
162
|
+
}
|
|
163
|
+
return readJobForgeRedactConfig(PROJECT_DIR);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function configPath(opts) {
|
|
167
|
+
return opts.config ? resolveInputPath(opts.config) : jobForgeRedactConfigPath(PROJECT_DIR);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function resolveInputPath(path) {
|
|
171
|
+
return isAbsolute(path) ? path : resolve(PROJECT_DIR, path);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function resolveOutputPath(path) {
|
|
175
|
+
return isAbsolute(path) ? path : resolve(PROJECT_DIR, path);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function relativePath(path) {
|
|
179
|
+
return relative(PROJECT_DIR, path) || '.';
|
|
180
|
+
}
|
|
@@ -40,6 +40,13 @@
|
|
|
40
40
|
"preflight:plan": "job-forge preflight:plan",
|
|
41
41
|
"preflight:check": "job-forge preflight:check",
|
|
42
42
|
"preflight:explain": "job-forge preflight:explain",
|
|
43
|
+
"postflight:status": "job-forge postflight:status",
|
|
44
|
+
"postflight:check": "job-forge postflight:check",
|
|
45
|
+
"postflight:explain": "job-forge postflight:explain",
|
|
46
|
+
"redact:scan": "job-forge redact:scan",
|
|
47
|
+
"redact:verify": "job-forge redact:verify",
|
|
48
|
+
"redact:apply": "job-forge redact:apply",
|
|
49
|
+
"redact:explain": "job-forge redact:explain",
|
|
43
50
|
"migrate:plan": "job-forge migrate:plan",
|
|
44
51
|
"migrate:apply": "job-forge migrate:apply",
|
|
45
52
|
"migrate:check": "job-forge migrate:check",
|
|
@@ -61,7 +68,11 @@
|
|
|
61
68
|
".jobforge-runs/",
|
|
62
69
|
".jobforge-ledger/",
|
|
63
70
|
".jobforge-cache/",
|
|
64
|
-
".jobforge-index.json"
|
|
71
|
+
".jobforge-index.json",
|
|
72
|
+
".jobforge-redacted/",
|
|
73
|
+
"batch/preflight-candidates.json",
|
|
74
|
+
"batch/preflight-plan.json",
|
|
75
|
+
"batch/postflight-outcomes.json"
|
|
65
76
|
]
|
|
66
77
|
}
|
|
67
78
|
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"workflows": [
|
|
4
|
+
{
|
|
5
|
+
"name": "jobforge.apply",
|
|
6
|
+
"description": "Settle JobForge application dispatch rounds after subagents return outcomes.",
|
|
7
|
+
"terminalStatuses": [
|
|
8
|
+
"APPLIED",
|
|
9
|
+
"Applied",
|
|
10
|
+
"APPLY FAILED",
|
|
11
|
+
"Apply Failed",
|
|
12
|
+
"FAILED",
|
|
13
|
+
"Failed",
|
|
14
|
+
"SKIP",
|
|
15
|
+
"Skip",
|
|
16
|
+
"Skipped",
|
|
17
|
+
"Discarded"
|
|
18
|
+
],
|
|
19
|
+
"successStatuses": [
|
|
20
|
+
"APPLIED",
|
|
21
|
+
"Applied"
|
|
22
|
+
],
|
|
23
|
+
"failureStatuses": [
|
|
24
|
+
"APPLY FAILED",
|
|
25
|
+
"Apply Failed",
|
|
26
|
+
"FAILED",
|
|
27
|
+
"Failed"
|
|
28
|
+
],
|
|
29
|
+
"skipStatuses": [
|
|
30
|
+
"SKIP",
|
|
31
|
+
"Skip",
|
|
32
|
+
"Skipped",
|
|
33
|
+
"Discarded"
|
|
34
|
+
],
|
|
35
|
+
"inFlightStatuses": [
|
|
36
|
+
"running",
|
|
37
|
+
"in-flight",
|
|
38
|
+
"started",
|
|
39
|
+
"pending"
|
|
40
|
+
],
|
|
41
|
+
"replacementStatuses": [
|
|
42
|
+
"APPLY FAILED",
|
|
43
|
+
"Apply Failed",
|
|
44
|
+
"FAILED",
|
|
45
|
+
"Failed"
|
|
46
|
+
],
|
|
47
|
+
"requiredArtifacts": [
|
|
48
|
+
{
|
|
49
|
+
"id": "tracker-tsv",
|
|
50
|
+
"label": "Tracker TSV outcome",
|
|
51
|
+
"statuses": [
|
|
52
|
+
"APPLIED",
|
|
53
|
+
"Applied",
|
|
54
|
+
"APPLY FAILED",
|
|
55
|
+
"Apply Failed",
|
|
56
|
+
"FAILED",
|
|
57
|
+
"Failed",
|
|
58
|
+
"SKIP",
|
|
59
|
+
"Skip",
|
|
60
|
+
"Skipped",
|
|
61
|
+
"Discarded"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"postSteps": [
|
|
66
|
+
{
|
|
67
|
+
"id": "merge",
|
|
68
|
+
"label": "Merge tracker TSV outcomes",
|
|
69
|
+
"command": "npx job-forge merge"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "verify",
|
|
73
|
+
"label": "Verify tracker integrity",
|
|
74
|
+
"command": "npx job-forge verify"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"defaults": {
|
|
4
|
+
"severity": "error",
|
|
5
|
+
"replacement": "[REDACTED:{id}]"
|
|
6
|
+
},
|
|
7
|
+
"builtins": [
|
|
8
|
+
{
|
|
9
|
+
"id": "email",
|
|
10
|
+
"severity": "warn"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "phone",
|
|
14
|
+
"severity": "warn"
|
|
15
|
+
},
|
|
16
|
+
"openai-api-key",
|
|
17
|
+
"github-token",
|
|
18
|
+
"npm-token",
|
|
19
|
+
"aws-access-key-id",
|
|
20
|
+
"bearer-token",
|
|
21
|
+
"private-key",
|
|
22
|
+
"proxy-url-credentials"
|
|
23
|
+
],
|
|
24
|
+
"fields": [
|
|
25
|
+
{
|
|
26
|
+
"id": "profile-contact",
|
|
27
|
+
"label": "Profile contact field",
|
|
28
|
+
"severity": "warn",
|
|
29
|
+
"names": [
|
|
30
|
+
"email",
|
|
31
|
+
"phone",
|
|
32
|
+
"address",
|
|
33
|
+
"street",
|
|
34
|
+
"city",
|
|
35
|
+
"postalCode",
|
|
36
|
+
"zip",
|
|
37
|
+
"linkedin"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "proxy-config",
|
|
42
|
+
"label": "Proxy configuration field",
|
|
43
|
+
"names": [
|
|
44
|
+
"proxy",
|
|
45
|
+
"proxyUrl",
|
|
46
|
+
"proxy_url",
|
|
47
|
+
"server",
|
|
48
|
+
"username",
|
|
49
|
+
"password",
|
|
50
|
+
"bypass"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "credential-field",
|
|
55
|
+
"label": "Credential field",
|
|
56
|
+
"names": [
|
|
57
|
+
"apiKey",
|
|
58
|
+
"api_key",
|
|
59
|
+
"access_token",
|
|
60
|
+
"refresh_token",
|
|
61
|
+
"client_secret",
|
|
62
|
+
"clientSecret",
|
|
63
|
+
"authorization",
|
|
64
|
+
"cookie",
|
|
65
|
+
"session",
|
|
66
|
+
"token"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"patterns": [
|
|
71
|
+
{
|
|
72
|
+
"id": "proxy-provider-account",
|
|
73
|
+
"label": "Proxy provider account identifier",
|
|
74
|
+
"pattern": "\\bbrd-customer-[A-Za-z0-9_.-]+\\b"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|