job-forge 2.14.27 → 2.14.28
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 +17 -1
- package/AGENTS.md +4 -1
- package/CLAUDE.md +4 -1
- package/README.md +5 -4
- package/bin/create-job-forge.mjs +5 -0
- package/bin/job-forge.mjs +28 -0
- package/docs/ARCHITECTURE.md +4 -1
- package/docs/CUSTOMIZATION.md +4 -0
- package/docs/README.md +1 -1
- package/docs/SETUP.md +2 -0
- package/iso/commands/job-forge.md +17 -1
- package/iso/instructions.md +4 -1
- package/lib/jobforge-preflight.mjs +29 -0
- package/package.json +5 -1
- package/scripts/preflight.mjs +142 -0
- package/templates/capabilities.json +5 -2
- package/templates/migrations.json +3 -0
- package/templates/preflight.json +59 -0
package/.cursor/rules/main.mdc
CHANGED
|
@@ -83,12 +83,15 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
83
83
|
- [D15] Treat `templates/canon.json` as the source of truth for URL/company/role identity keys. Use `npx job-forge canon:key ...` or `npx job-forge canon:compare ...` before broad duplicate checks when a stable key or same/possible/different decision is useful.
|
|
84
84
|
why: `iso-canon` is not an MCP and adds no prompt/tool-schema tokens; it centralizes duplicate-key rules so agents do not repeatedly derive inconsistent slugs for aliases, suffixes, remote/location noise, or tracking URLs
|
|
85
85
|
|
|
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
|
+
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
|
+
|
|
86
89
|
## Procedure
|
|
87
90
|
|
|
88
91
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
89
92
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
90
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].
|
|
91
|
-
4. Prepare Geometra dispatches: cleanup [H3], canon/index/ledger prefilter when useful [D8, D13, D15], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
|
|
94
|
+
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].
|
|
92
95
|
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
93
96
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
94
97
|
7. Cross-check subagent facts against authoritative files [H7].
|
|
@@ -82,6 +82,10 @@ Identity keys (terminal, outside opencode):
|
|
|
82
82
|
npx job-forge canon:key company-role --company "Acme" --role "Staff Engineer"
|
|
83
83
|
npx job-forge canon:compare company "OpenAI, Inc." "Open AI"
|
|
84
84
|
|
|
85
|
+
Preflight dispatch plans (terminal, outside opencode):
|
|
86
|
+
npx job-forge preflight:plan --candidates batch/preflight-candidates.json
|
|
87
|
+
npx job-forge preflight:check --candidates batch/preflight-candidates.json
|
|
88
|
+
|
|
85
89
|
Consumer migrations (terminal, outside opencode):
|
|
86
90
|
npx job-forge migrate:plan # preview package.json/.gitignore drift
|
|
87
91
|
npx job-forge migrate:apply # apply safe harness upgrade migrations
|
|
@@ -194,7 +198,19 @@ Step 3 — Pre-flight cleanup (once, before the loop)
|
|
|
194
198
|
- geometra_list_sessions()
|
|
195
199
|
- geometra_disconnect({ closeBrowser: true })
|
|
196
200
|
|
|
197
|
-
Step 4 —
|
|
201
|
+
Step 4 — Materialize and check the dispatch plan
|
|
202
|
+
- Write file-backed candidate facts/gates to batch/preflight-candidates.json
|
|
203
|
+
(or another explicit JSON file). Include source paths for company, role,
|
|
204
|
+
companyRoleKey, URL, score, duplicate/location gates, and any skip/block
|
|
205
|
+
decision.
|
|
206
|
+
- Run npx job-forge preflight:check --candidates <file> to fail on missing
|
|
207
|
+
sources or blocked gates, then npx job-forge preflight:plan --candidates
|
|
208
|
+
<file> to get the bounded round list.
|
|
209
|
+
- Follow the emitted rounds. Do not dispatch blocked candidates, and do not
|
|
210
|
+
replace H2's four-source grep with preflight unless those grep results are
|
|
211
|
+
present in the candidate JSON.
|
|
212
|
+
|
|
213
|
+
Step 5 — Loop in rounds of 2 (Hard Limit #1)
|
|
198
214
|
for round in ceil(len(candidates) / 2):
|
|
199
215
|
pair = candidates[round*2 : round*2 + 2]
|
|
200
216
|
# If proxy is configured, do not paste proxy values into prompts.
|
package/AGENTS.md
CHANGED
|
@@ -78,12 +78,15 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
78
78
|
- [D15] Treat `templates/canon.json` as the source of truth for URL/company/role identity keys. Use `npx job-forge canon:key ...` or `npx job-forge canon:compare ...` before broad duplicate checks when a stable key or same/possible/different decision is useful.
|
|
79
79
|
why: `iso-canon` is not an MCP and adds no prompt/tool-schema tokens; it centralizes duplicate-key rules so agents do not repeatedly derive inconsistent slugs for aliases, suffixes, remote/location noise, or tracking URLs
|
|
80
80
|
|
|
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
|
+
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
|
+
|
|
81
84
|
## Procedure
|
|
82
85
|
|
|
83
86
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
84
87
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
85
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].
|
|
86
|
-
4. Prepare Geometra dispatches: cleanup [H3], canon/index/ledger prefilter when useful [D8, D13, D15], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
|
|
89
|
+
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].
|
|
87
90
|
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
88
91
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
89
92
|
7. Cross-check subagent facts against authoritative files [H7].
|
package/CLAUDE.md
CHANGED
|
@@ -78,12 +78,15 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
78
78
|
- [D15] Treat `templates/canon.json` as the source of truth for URL/company/role identity keys. Use `npx job-forge canon:key ...` or `npx job-forge canon:compare ...` before broad duplicate checks when a stable key or same/possible/different decision is useful.
|
|
79
79
|
why: `iso-canon` is not an MCP and adds no prompt/tool-schema tokens; it centralizes duplicate-key rules so agents do not repeatedly derive inconsistent slugs for aliases, suffixes, remote/location noise, or tracking URLs
|
|
80
80
|
|
|
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
|
+
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
|
+
|
|
81
84
|
## Procedure
|
|
82
85
|
|
|
83
86
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
84
87
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
85
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].
|
|
86
|
-
4. Prepare Geometra dispatches: cleanup [H3], canon/index/ledger prefilter when useful [D8, D13, D15], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
|
|
89
|
+
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].
|
|
87
90
|
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
88
91
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
89
92
|
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/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/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/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 + 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, 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 + 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. |
|
|
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
|
|
@@ -164,7 +164,7 @@ my-search/
|
|
|
164
164
|
├── .opencode/skills/job-forge.md # → skill router
|
|
165
165
|
├── .opencode/agents/ # → @general-free, @general-paid, @glm-minimal
|
|
166
166
|
├── modes/ # → _shared.md + skill modes
|
|
167
|
-
├── templates/ # → states.yml, portals.example.yml, cv-template.html, canon.json, capabilities.json, context.json, index.json, migrations.json
|
|
167
|
+
├── templates/ # → states.yml, portals.example.yml, cv-template.html, canon.json, capabilities.json, context.json, index.json, preflight.json, migrations.json
|
|
168
168
|
├── batch/batch-prompt.md # → batch worker prompt
|
|
169
169
|
├── batch/batch-runner.sh # → parallel orchestrator
|
|
170
170
|
│
|
|
@@ -190,7 +190,7 @@ JobForge/
|
|
|
190
190
|
│ ├── sync.mjs # postinstall: creates symlinks in consumer project
|
|
191
191
|
│ └── create-job-forge.mjs # scaffolder
|
|
192
192
|
├── modes/ # _shared.md + 16 skill modes
|
|
193
|
-
├── templates/ # cv-template.html, portals.example.yml, states.yml, canon.json, capabilities.json, context.json, migrations.json
|
|
193
|
+
├── templates/ # cv-template.html, portals.example.yml, states.yml, canon.json, capabilities.json, context.json, preflight.json, migrations.json
|
|
194
194
|
├── config/profile.example.yml # template for consumer's profile.yml
|
|
195
195
|
├── batch/{batch-prompt.md,batch-runner.sh} # batch orchestrator
|
|
196
196
|
├── scripts/
|
|
@@ -202,6 +202,7 @@ JobForge/
|
|
|
202
202
|
│ ├── cache.mjs # iso-cache-backed local artifact cache CLI
|
|
203
203
|
│ ├── index.mjs # iso-index-backed artifact lookup CLI
|
|
204
204
|
│ ├── canon.mjs # iso-canon-backed identity normalization CLI
|
|
205
|
+
│ ├── preflight.mjs # iso-preflight-backed dispatch planning CLI
|
|
205
206
|
│ ├── migrate.mjs # iso-migrate-backed consumer-project migrations
|
|
206
207
|
│ ├── token-usage-report.mjs # opencode cost analyzer
|
|
207
208
|
│ └── release/check-source.mjs # version gate for npm publish
|
package/bin/create-job-forge.mjs
CHANGED
|
@@ -151,6 +151,9 @@ const consumerPkg = {
|
|
|
151
151
|
'canon:key': 'job-forge canon:key',
|
|
152
152
|
'canon:compare': 'job-forge canon:compare',
|
|
153
153
|
'canon:explain': 'job-forge canon:explain',
|
|
154
|
+
'preflight:plan': 'job-forge preflight:plan',
|
|
155
|
+
'preflight:check': 'job-forge preflight:check',
|
|
156
|
+
'preflight:explain': 'job-forge preflight:explain',
|
|
154
157
|
'migrate:plan': 'job-forge migrate:plan',
|
|
155
158
|
'migrate:apply': 'job-forge migrate:apply',
|
|
156
159
|
'migrate:check': 'job-forge migrate:check',
|
|
@@ -257,6 +260,7 @@ Before doing any work, remember where things live in *this* project:
|
|
|
257
260
|
| Local workflow ledger | \`.jobforge-ledger/events.jsonl\` | Deterministic append-only state; use \`job-forge ledger:*\` |
|
|
258
261
|
| Local artifact index | \`.jobforge-index.json\` | Deterministic file/line lookup; use \`job-forge index:*\` |
|
|
259
262
|
| Identity canonicalization | \`templates/canon.json\` | Stable URL/company/role keys; use \`job-forge canon:*\` |
|
|
263
|
+
| Dispatch preflight policy | \`templates/preflight.json\` | Safe apply rounds/gates; use \`job-forge preflight:*\` |
|
|
260
264
|
| Consumer migrations | \`templates/migrations.json\` | Safe script/gitignore upgrades; use \`job-forge migrate:*\` |
|
|
261
265
|
| Scanner config | \`portals.yml\` (project root) | Company configs |
|
|
262
266
|
| Profile / identity | \`config/profile.yml\` | Candidate name, email, target roles |
|
|
@@ -408,6 +412,7 @@ job-forge verify # verify pipeline integrity
|
|
|
408
412
|
job-forge ledger:status # local deterministic workflow ledger status
|
|
409
413
|
job-forge index:status # local artifact index status
|
|
410
414
|
job-forge canon:key company-role --company "Acme, Inc." --role "Senior SWE"
|
|
415
|
+
job-forge preflight:plan --candidates batch/preflight-candidates.json
|
|
411
416
|
job-forge migrate:check # verify consumer package scripts/gitignore are current
|
|
412
417
|
job-forge pdf cv.md out.pdf
|
|
413
418
|
job-forge tokens --days 1 # per-session opencode token usage
|
package/bin/job-forge.mjs
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* cache:* Reuse local deterministic artifacts via iso-cache
|
|
27
27
|
* index:* Query local artifacts via iso-index
|
|
28
28
|
* canon:* Compute deterministic identity keys via iso-canon
|
|
29
|
+
* preflight:* Plan safe dispatch rounds via iso-preflight
|
|
29
30
|
* migrate:* Apply deterministic consumer-project migrations via iso-migrate
|
|
30
31
|
* sync Re-run the harness symlink sync (bin/sync.mjs)
|
|
31
32
|
* help, --help Show this message
|
|
@@ -137,6 +138,13 @@ const canonAliases = {
|
|
|
137
138
|
'canon:path': 'path',
|
|
138
139
|
};
|
|
139
140
|
|
|
141
|
+
const preflightAliases = {
|
|
142
|
+
'preflight:plan': 'plan',
|
|
143
|
+
'preflight:check': 'check',
|
|
144
|
+
'preflight:explain': 'explain',
|
|
145
|
+
'preflight:path': 'path',
|
|
146
|
+
};
|
|
147
|
+
|
|
140
148
|
const migrateAliases = {
|
|
141
149
|
'migrate:plan': 'plan',
|
|
142
150
|
'migrate:apply': 'apply',
|
|
@@ -198,6 +206,9 @@ Commands:
|
|
|
198
206
|
canon:key Print stable URL/company/role/company-role keys
|
|
199
207
|
canon:compare Compare two identifiers as same/possible/different
|
|
200
208
|
canon:explain Show the active identity canonicalization policy
|
|
209
|
+
preflight:plan Build bounded dispatch plan from candidate JSON
|
|
210
|
+
preflight:check Fail if preflight candidates are blocked
|
|
211
|
+
preflight:explain Show the active preflight workflow policy
|
|
201
212
|
migrate:plan Preview deterministic consumer-project migrations
|
|
202
213
|
migrate:apply Apply deterministic consumer-project migrations
|
|
203
214
|
migrate:check Fail if migrations are pending
|
|
@@ -242,6 +253,8 @@ Pass --help after a command to see its own flags, e.g.:
|
|
|
242
253
|
job-forge index:query "acme"
|
|
243
254
|
job-forge canon:key company-role --company "Acme, Inc." --role "Senior SWE - Remote US"
|
|
244
255
|
job-forge canon:compare company "OpenAI, Inc." "Open AI"
|
|
256
|
+
job-forge preflight:plan --candidates batch/preflight-candidates.json
|
|
257
|
+
job-forge preflight:check --candidates batch/preflight-candidates.json
|
|
245
258
|
job-forge migrate:check
|
|
246
259
|
job-forge migrate:apply
|
|
247
260
|
|
|
@@ -388,6 +401,21 @@ if (cmd === 'canon' || canonAliases[cmd]) {
|
|
|
388
401
|
process.exit(result.status ?? 1);
|
|
389
402
|
}
|
|
390
403
|
|
|
404
|
+
if (cmd === 'preflight' || preflightAliases[cmd]) {
|
|
405
|
+
const preflightArgs = cmd === 'preflight'
|
|
406
|
+
? (rest.length === 0 ? ['help'] : rest)
|
|
407
|
+
: [preflightAliases[cmd], ...rest];
|
|
408
|
+
|
|
409
|
+
const scriptPath = join(PKG_ROOT, 'scripts/preflight.mjs');
|
|
410
|
+
const result = spawnSync(process.execPath, [scriptPath, ...preflightArgs], {
|
|
411
|
+
stdio: 'inherit',
|
|
412
|
+
cwd: PROJECT_DIR,
|
|
413
|
+
env: process.env,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
process.exit(result.status ?? 1);
|
|
417
|
+
}
|
|
418
|
+
|
|
391
419
|
if (cmd === 'migrate' || migrateAliases[cmd]) {
|
|
392
420
|
const migrateArgs = cmd === 'migrate'
|
|
393
421
|
? (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
|
|
35
|
+
├── templates/ # → states.yml, portals.example.yml, cv-template.html, preflight.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
|
|
@@ -166,6 +166,7 @@ jds/*.md → Saved job descriptions referenced from the pipelin
|
|
|
166
166
|
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
|
+
templates/preflight.json → Safe apply dispatch rounds/gates policy
|
|
169
170
|
templates/migrations.json → Safe consumer-project upgrade policy
|
|
170
171
|
templates/cv-template.html → PDF generation template
|
|
171
172
|
examples/*.md → Fictional layouts only (not read by scripts; see examples/README.md)
|
|
@@ -181,6 +182,7 @@ Create `data/pipeline.md` when you start using the URL inbox (`/job-forge pipeli
|
|
|
181
182
|
- Ledger: `.jobforge-ledger/events.jsonl` (created by `job-forge ledger:rebuild`, `tracker-line --write`, or `merge`; gitignored personal state)
|
|
182
183
|
- Index: `.jobforge-index.json` (created on demand by `job-forge index:*`; gitignored local lookup state)
|
|
183
184
|
- Canon: `templates/canon.json` (identity rules inspected with `job-forge canon:*`)
|
|
185
|
+
- Preflight: `templates/preflight.json` (dispatch rounds/gates inspected with `job-forge preflight:*`)
|
|
184
186
|
- Migrations: `templates/migrations.json` (applied by `job-forge sync` and inspectable with `job-forge migrate:*`)
|
|
185
187
|
- Capabilities: `templates/capabilities.json` (role boundary policy inspected with `job-forge capabilities:*`)
|
|
186
188
|
- Context: `templates/context.json` (mode/reference file bundles inspected with `job-forge context:*`)
|
|
@@ -229,6 +231,7 @@ Scripts maintain data consistency. In a consumer project they're invoked via the
|
|
|
229
231
|
| `scripts/index.mjs` | `npx job-forge index:status` / `index:has` / `index:query` | Deterministic `@razroo/iso-index` lookup over reports, tracker rows, TSVs, pipeline, scan history, and ledger events |
|
|
230
232
|
| `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 |
|
|
231
233
|
| `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
|
+
| `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 |
|
|
232
235
|
| `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 |
|
|
233
236
|
| `tracker-lib.mjs` | _(library)_ | Shared helpers for reading/writing day-based tracker files — imported by merge/dedup/verify/normalize |
|
|
234
237
|
| `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
|
@@ -158,6 +158,10 @@ Artifact lookup policy lives in `templates/index.json` and is built locally by `
|
|
|
158
158
|
|
|
159
159
|
URL, company, role, and company+role identity rules live in `templates/canon.json` and are enforced locally by `@razroo/iso-canon`. Use `job-forge canon:key company-role --company "OpenAI, Inc." --role "Senior SWE, AI Platform"` to derive the same duplicate key used by ledger/index helpers, and `job-forge canon:compare company "OpenAI, Inc." "Open AI"` to explain whether two values resolve to the same entity. Custom forks can extend aliases, suffixes, stop words, and match thresholds in `templates/canon.json`. This is not an MCP and does not add prompt or tool-schema tokens.
|
|
160
160
|
|
|
161
|
+
## JobForge preflight plans
|
|
162
|
+
|
|
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
|
+
|
|
161
165
|
## JobForge consumer migrations
|
|
162
166
|
|
|
163
167
|
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`, `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`, `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
|
@@ -132,6 +132,8 @@ From your project root, these commands maintain the tracker and pipeline checks.
|
|
|
132
132
|
| Inspect context bundle budget | `npx job-forge context:plan apply` | `npm run context:plan -- apply` |
|
|
133
133
|
| Inspect local JD/artifact cache | `npx job-forge cache:status` | `npm run cache:status` |
|
|
134
134
|
| Inspect local artifact index | `npx job-forge index:status` | `npm run index:status` |
|
|
135
|
+
| Plan safe application dispatch rounds | `npx job-forge preflight:plan --candidates batch/preflight-candidates.json` | `npm run preflight:plan -- --candidates ...` |
|
|
136
|
+
| Fail on blocked preflight candidates | `npx job-forge preflight:check --candidates batch/preflight-candidates.json` | `npm run preflight:check -- --candidates ...` |
|
|
135
137
|
| Inspect pending consumer migrations | `npx job-forge migrate:plan` | `npm run migrate:plan` |
|
|
136
138
|
| Map status column to canonical labels | `npx job-forge normalize` | `npm run normalize` |
|
|
137
139
|
| Merge duplicate company/role rows | `npx job-forge dedup` | `npm run dedup` |
|
|
@@ -85,6 +85,10 @@ Identity keys (terminal, outside opencode):
|
|
|
85
85
|
npx job-forge canon:key company-role --company "Acme" --role "Staff Engineer"
|
|
86
86
|
npx job-forge canon:compare company "OpenAI, Inc." "Open AI"
|
|
87
87
|
|
|
88
|
+
Preflight dispatch plans (terminal, outside opencode):
|
|
89
|
+
npx job-forge preflight:plan --candidates batch/preflight-candidates.json
|
|
90
|
+
npx job-forge preflight:check --candidates batch/preflight-candidates.json
|
|
91
|
+
|
|
88
92
|
Consumer migrations (terminal, outside opencode):
|
|
89
93
|
npx job-forge migrate:plan # preview package.json/.gitignore drift
|
|
90
94
|
npx job-forge migrate:apply # apply safe harness upgrade migrations
|
|
@@ -197,7 +201,19 @@ Step 3 — Pre-flight cleanup (once, before the loop)
|
|
|
197
201
|
- geometra_list_sessions()
|
|
198
202
|
- geometra_disconnect({ closeBrowser: true })
|
|
199
203
|
|
|
200
|
-
Step 4 —
|
|
204
|
+
Step 4 — Materialize and check the dispatch plan
|
|
205
|
+
- Write file-backed candidate facts/gates to batch/preflight-candidates.json
|
|
206
|
+
(or another explicit JSON file). Include source paths for company, role,
|
|
207
|
+
companyRoleKey, URL, score, duplicate/location gates, and any skip/block
|
|
208
|
+
decision.
|
|
209
|
+
- Run npx job-forge preflight:check --candidates <file> to fail on missing
|
|
210
|
+
sources or blocked gates, then npx job-forge preflight:plan --candidates
|
|
211
|
+
<file> to get the bounded round list.
|
|
212
|
+
- Follow the emitted rounds. Do not dispatch blocked candidates, and do not
|
|
213
|
+
replace H2's four-source grep with preflight unless those grep results are
|
|
214
|
+
present in the candidate JSON.
|
|
215
|
+
|
|
216
|
+
Step 5 — Loop in rounds of 2 (Hard Limit #1)
|
|
201
217
|
for round in ceil(len(candidates) / 2):
|
|
202
218
|
pair = candidates[round*2 : round*2 + 2]
|
|
203
219
|
# If proxy is configured, do not paste proxy values into prompts.
|
package/iso/instructions.md
CHANGED
|
@@ -78,12 +78,15 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
|
|
|
78
78
|
- [D15] Treat `templates/canon.json` as the source of truth for URL/company/role identity keys. Use `npx job-forge canon:key ...` or `npx job-forge canon:compare ...` before broad duplicate checks when a stable key or same/possible/different decision is useful.
|
|
79
79
|
why: `iso-canon` is not an MCP and adds no prompt/tool-schema tokens; it centralizes duplicate-key rules so agents do not repeatedly derive inconsistent slugs for aliases, suffixes, remote/location noise, or tracking URLs
|
|
80
80
|
|
|
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
|
+
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
|
+
|
|
81
84
|
## Procedure
|
|
82
85
|
|
|
83
86
|
1. Check `cv.md`, `profile.yml`, and `portals.yml`; onboard if any file is missing.
|
|
84
87
|
2. Pick and name the mode from **Routing** [D6]. No match → ask; do not guess.
|
|
85
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].
|
|
86
|
-
4. Prepare Geometra dispatches: cleanup [H3], canon/index/ledger prefilter when useful [D8, D13, D15], dedupe [H2], location filter [D5], routing [D2, D10], proxy prompt hygiene [H8].
|
|
89
|
+
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].
|
|
87
90
|
5. Dispatch at most 2 tasks per round [H1]; wait for final outcomes, not just task ids [H5b].
|
|
88
91
|
6. Keep multi-job form-filling out of the orchestrator [H4].
|
|
89
92
|
7. Cross-check subagent facts against authoritative files [H7].
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import {
|
|
4
|
+
loadPreflightConfig,
|
|
5
|
+
parseJson,
|
|
6
|
+
planPreflight,
|
|
7
|
+
} from '@razroo/iso-preflight';
|
|
8
|
+
|
|
9
|
+
export const PREFLIGHT_CONFIG_FILE = 'templates/preflight.json';
|
|
10
|
+
export const PREFLIGHT_WORKFLOW = 'jobforge.apply';
|
|
11
|
+
|
|
12
|
+
export function resolveProjectDir(projectDir = process.env.JOB_FORGE_PROJECT || process.cwd()) {
|
|
13
|
+
return projectDir;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function jobForgePreflightConfigPath(projectDir = resolveProjectDir()) {
|
|
17
|
+
return process.env.JOB_FORGE_PREFLIGHT_CONFIG || join(projectDir, PREFLIGHT_CONFIG_FILE);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readJobForgePreflightConfig(projectDir = resolveProjectDir()) {
|
|
21
|
+
const path = jobForgePreflightConfigPath(projectDir);
|
|
22
|
+
return loadPreflightConfig(parseJson(readFileSync(path, 'utf8'), path));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function planJobForgePreflight(candidateInput, options = {}, projectDir = resolveProjectDir()) {
|
|
26
|
+
return planPreflight(readJobForgePreflightConfig(projectDir), candidateInput, {
|
|
27
|
+
workflow: options.workflow || PREFLIGHT_WORKFLOW,
|
|
28
|
+
});
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-forge",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.28",
|
|
4
4
|
"description": "AI-powered job search pipeline built on opencode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -59,6 +59,9 @@
|
|
|
59
59
|
"canon:key": "node bin/job-forge.mjs canon:key",
|
|
60
60
|
"canon:compare": "node bin/job-forge.mjs canon:compare",
|
|
61
61
|
"canon:explain": "node bin/job-forge.mjs canon:explain",
|
|
62
|
+
"preflight:plan": "node bin/job-forge.mjs preflight:plan",
|
|
63
|
+
"preflight:check": "node bin/job-forge.mjs preflight:check",
|
|
64
|
+
"preflight:explain": "node bin/job-forge.mjs preflight:explain",
|
|
62
65
|
"migrate:plan": "node bin/job-forge.mjs migrate:plan",
|
|
63
66
|
"migrate:apply": "node bin/job-forge.mjs migrate:apply",
|
|
64
67
|
"migrate:check": "node bin/job-forge.mjs migrate:check",
|
|
@@ -139,6 +142,7 @@
|
|
|
139
142
|
"@razroo/iso-ledger": "^0.1.0",
|
|
140
143
|
"@razroo/iso-migrate": "^0.1.0",
|
|
141
144
|
"@razroo/iso-orchestrator": "^0.1.0",
|
|
145
|
+
"@razroo/iso-preflight": "^0.1.0",
|
|
142
146
|
"@razroo/iso-trace": "^0.4.0",
|
|
143
147
|
"playwright": "^1.58.1"
|
|
144
148
|
},
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { isAbsolute, relative, resolve } from 'path';
|
|
5
|
+
import {
|
|
6
|
+
formatConfigSummary,
|
|
7
|
+
formatPreflightPlan,
|
|
8
|
+
loadPreflightConfig,
|
|
9
|
+
parseJson,
|
|
10
|
+
planPreflight,
|
|
11
|
+
} from '@razroo/iso-preflight';
|
|
12
|
+
import { PROJECT_DIR } from '../tracker-lib.mjs';
|
|
13
|
+
import {
|
|
14
|
+
jobForgePreflightConfigPath,
|
|
15
|
+
planJobForgePreflight,
|
|
16
|
+
readJobForgePreflightConfig,
|
|
17
|
+
} from '../lib/jobforge-preflight.mjs';
|
|
18
|
+
|
|
19
|
+
const USAGE = `job-forge preflight - deterministic dispatch planning for JobForge
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
job-forge preflight:plan --candidates <file> [--workflow jobforge.apply] [--json]
|
|
23
|
+
job-forge preflight:check --candidates <file> [--workflow jobforge.apply] [--json]
|
|
24
|
+
job-forge preflight:explain [--json]
|
|
25
|
+
job-forge preflight:path
|
|
26
|
+
|
|
27
|
+
Candidate files are JSON arrays, or objects with a candidates array. The policy
|
|
28
|
+
is templates/preflight.json. This is local project state, not an MCP and not
|
|
29
|
+
prompt context.`;
|
|
30
|
+
|
|
31
|
+
const [cmd = 'help', ...rawArgs] = process.argv.slice(2);
|
|
32
|
+
const opts = parseArgs(rawArgs);
|
|
33
|
+
|
|
34
|
+
if (opts.help || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
35
|
+
console.log(USAGE);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
if (cmd === 'path') {
|
|
41
|
+
console.log(configPath(opts));
|
|
42
|
+
} else if (cmd === 'plan' || cmd === 'check') {
|
|
43
|
+
runPlan(cmd, opts);
|
|
44
|
+
} else if (cmd === 'explain') {
|
|
45
|
+
explain(opts);
|
|
46
|
+
} else {
|
|
47
|
+
console.error(`unknown preflight command "${cmd}"\n`);
|
|
48
|
+
console.error(USAGE);
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseArgs(args) {
|
|
57
|
+
const opts = {
|
|
58
|
+
json: false,
|
|
59
|
+
help: false,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < args.length; i++) {
|
|
63
|
+
const arg = args[i];
|
|
64
|
+
if (arg === '--json') {
|
|
65
|
+
opts.json = true;
|
|
66
|
+
} else if (arg === '--candidates' || arg === '-c') {
|
|
67
|
+
opts.candidates = valueAfter(args, ++i, arg);
|
|
68
|
+
} else if (arg.startsWith('--candidates=')) {
|
|
69
|
+
opts.candidates = arg.slice('--candidates='.length);
|
|
70
|
+
} else if (arg === '--workflow') {
|
|
71
|
+
opts.workflow = valueAfter(args, ++i, '--workflow');
|
|
72
|
+
} else if (arg.startsWith('--workflow=')) {
|
|
73
|
+
opts.workflow = arg.slice('--workflow='.length);
|
|
74
|
+
} else if (arg === '--config') {
|
|
75
|
+
opts.config = valueAfter(args, ++i, '--config');
|
|
76
|
+
} else if (arg.startsWith('--config=')) {
|
|
77
|
+
opts.config = arg.slice('--config='.length);
|
|
78
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
79
|
+
opts.help = true;
|
|
80
|
+
} else {
|
|
81
|
+
throw new Error(`unknown flag "${arg}"`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return opts;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runPlan(mode, opts) {
|
|
89
|
+
if (!opts.candidates) throw new Error(`${mode} requires --candidates <file>`);
|
|
90
|
+
const candidates = readJsonFile(resolveInputPath(opts.candidates));
|
|
91
|
+
const result = opts.config
|
|
92
|
+
? planPreflight(readConfig(opts), candidates, { workflow: opts.workflow })
|
|
93
|
+
: planJobForgePreflight(candidates, { workflow: opts.workflow }, PROJECT_DIR);
|
|
94
|
+
|
|
95
|
+
if (opts.json) {
|
|
96
|
+
console.log(JSON.stringify(result, null, 2));
|
|
97
|
+
} else {
|
|
98
|
+
console.log(formatPreflightPlan(result, mode));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (mode === 'check' && !result.ok) process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function explain(opts) {
|
|
105
|
+
const config = readConfig(opts);
|
|
106
|
+
if (opts.json) {
|
|
107
|
+
console.log(JSON.stringify(config, null, 2));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
console.log(`config: ${relativePath(configPath(opts))}`);
|
|
111
|
+
console.log(formatConfigSummary(config));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readConfig(opts) {
|
|
115
|
+
if (opts.config) {
|
|
116
|
+
const path = resolveInputPath(opts.config);
|
|
117
|
+
return loadPreflightConfig(readJsonFile(path));
|
|
118
|
+
}
|
|
119
|
+
return readJobForgePreflightConfig(PROJECT_DIR);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function configPath(opts) {
|
|
123
|
+
return opts.config ? resolveInputPath(opts.config) : jobForgePreflightConfigPath(PROJECT_DIR);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function readJsonFile(path) {
|
|
127
|
+
return parseJson(readFileSync(path, 'utf8'), path);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function valueAfter(values, index, flag) {
|
|
131
|
+
const value = values[index];
|
|
132
|
+
if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolveInputPath(path) {
|
|
137
|
+
return isAbsolute(path) ? path : resolve(PROJECT_DIR, path);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function relativePath(path) {
|
|
141
|
+
return relative(PROJECT_DIR, path) || '.';
|
|
142
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"npx job-forge cache:*",
|
|
15
15
|
"npx job-forge index:*",
|
|
16
16
|
"npx job-forge canon:*",
|
|
17
|
+
"npx job-forge preflight:*",
|
|
17
18
|
"rg *"
|
|
18
19
|
],
|
|
19
20
|
"deny": [
|
|
@@ -37,7 +38,8 @@
|
|
|
37
38
|
"npx job-forge merge",
|
|
38
39
|
"npx job-forge guard:*",
|
|
39
40
|
"npx job-forge telemetry:*",
|
|
40
|
-
"npx job-forge trace:*"
|
|
41
|
+
"npx job-forge trace:*",
|
|
42
|
+
"npx job-forge preflight:*"
|
|
41
43
|
]
|
|
42
44
|
},
|
|
43
45
|
"filesystem": "read-only",
|
|
@@ -62,7 +64,8 @@
|
|
|
62
64
|
"npx job-forge capabilities:*",
|
|
63
65
|
"npx job-forge cache:*",
|
|
64
66
|
"npx job-forge index:*",
|
|
65
|
-
"npx job-forge canon:*"
|
|
67
|
+
"npx job-forge canon:*",
|
|
68
|
+
"npx job-forge preflight:*"
|
|
66
69
|
],
|
|
67
70
|
"deny": [
|
|
68
71
|
"task *"
|
|
@@ -37,6 +37,9 @@
|
|
|
37
37
|
"canon:key": "job-forge canon:key",
|
|
38
38
|
"canon:compare": "job-forge canon:compare",
|
|
39
39
|
"canon:explain": "job-forge canon:explain",
|
|
40
|
+
"preflight:plan": "job-forge preflight:plan",
|
|
41
|
+
"preflight:check": "job-forge preflight:check",
|
|
42
|
+
"preflight:explain": "job-forge preflight:explain",
|
|
40
43
|
"migrate:plan": "job-forge migrate:plan",
|
|
41
44
|
"migrate:apply": "job-forge migrate:apply",
|
|
42
45
|
"migrate:check": "job-forge migrate:check",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"workflows": [
|
|
4
|
+
{
|
|
5
|
+
"name": "jobforge.apply",
|
|
6
|
+
"description": "Plan safe JobForge application dispatch rounds before Geometra/task work starts.",
|
|
7
|
+
"roundSize": 2,
|
|
8
|
+
"idFact": "id",
|
|
9
|
+
"conflictFact": "companyRoleKey",
|
|
10
|
+
"requiredFacts": [
|
|
11
|
+
"id",
|
|
12
|
+
"company",
|
|
13
|
+
"role",
|
|
14
|
+
"companyRoleKey",
|
|
15
|
+
"url",
|
|
16
|
+
"score"
|
|
17
|
+
],
|
|
18
|
+
"sourceRequiredFacts": [
|
|
19
|
+
"company",
|
|
20
|
+
"role",
|
|
21
|
+
"companyRoleKey",
|
|
22
|
+
"url",
|
|
23
|
+
"score"
|
|
24
|
+
],
|
|
25
|
+
"requireGateSources": true,
|
|
26
|
+
"gatePolicy": {
|
|
27
|
+
"skipStatuses": [
|
|
28
|
+
"skip",
|
|
29
|
+
"skipped"
|
|
30
|
+
],
|
|
31
|
+
"blockStatuses": [
|
|
32
|
+
"block",
|
|
33
|
+
"blocked",
|
|
34
|
+
"fail",
|
|
35
|
+
"failed"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"preSteps": [
|
|
39
|
+
{
|
|
40
|
+
"id": "geometra-cleanup",
|
|
41
|
+
"label": "Disconnect stale Geometra sessions before dispatch",
|
|
42
|
+
"command": "geometra_list_sessions && geometra_disconnect({ closeBrowser: true })"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"postSteps": [
|
|
46
|
+
{
|
|
47
|
+
"id": "merge",
|
|
48
|
+
"label": "Merge tracker TSV outcomes",
|
|
49
|
+
"command": "npx job-forge merge"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "verify",
|
|
53
|
+
"label": "Verify tracker integrity",
|
|
54
|
+
"command": "npx job-forge verify"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|