job-forge 2.14.11 → 2.14.13

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.
@@ -1,24 +1,16 @@
1
1
  ---
2
- description: Procedural worker on free-tier model. Use for form filling via Geometra, tracker updates, TSV merges, scan dedup, OTP retrieval, and other mechanical/scripted tasks where quality-sensitive text generation is NOT required.
2
+ description: Procedural worker on the low-cost DeepSeek V4 Flash OpenCode route. Use for form filling via Geometra, tracker updates, TSV merges, scan dedup, OTP retrieval, and other mechanical/scripted tasks where quality-sensitive text generation is NOT required.
3
3
  role: fast
4
4
  targets:
5
- # No inline model: iso-route's "standard" preset maps role "fast" to
6
- # each harness's cheapest reliable model. Claude Code reads
5
+ # No inline model: JobForge's models.yaml maps role "fast" to each
6
+ # harness's cheapest reliable model. On OpenCode that is pinned to
7
+ # opencode-go/deepseek-v4-flash. Claude Code reads
7
8
  # .claude/iso-route.resolved.json; OpenCode reads opencode.json's
8
9
  # agent.fast.model (iso-harness 0.6.0+).
9
10
  opencode:
10
11
  mode: subagent
11
12
  temperature: 0.1
12
13
  reasoningEffort: minimal
13
- # Primary comes from models.yaml: opencode/big-pickle on OpenCode.
14
- # Fallback chain stays free-only and intentionally excludes
15
- # openrouter/minimax/minimax-m2.5:free because recent traces showed
16
- # repeated read({ path|file_path }) schema drift on that route.
17
- fallback_models:
18
- - openrouter/z-ai/glm-4.5-air:free
19
- - openrouter/openai/gpt-oss-20b:free
20
- - openrouter/nvidia/nemotron-3-nano-30b-a3b:free
21
- - openrouter/qwen/qwen3-coder:free
22
14
  tools:
23
15
  geometra_connect: true
24
16
  geometra_page_model: true
@@ -34,7 +26,7 @@ targets:
34
26
  task: false
35
27
  ---
36
28
 
37
- You are the @general-free subagent. You run on a free-tier model, which means the orchestrator has delegated this task to you **specifically because the work is procedural**: deterministic steps, scripted outputs, no nuanced writing required.
29
+ You are the @general-free subagent. You run on JobForge's low-cost procedural model, which means the orchestrator has delegated this task to you **specifically because the work is procedural**: deterministic steps, scripted outputs, no nuanced writing required.
38
30
 
39
31
  ## Run This Pre-Flight First Every Time
40
32
 
@@ -1,28 +1,16 @@
1
1
  ---
2
- description: Quality-sensitive worker on the strongest free-tier OpenCode model by default. Use for offer evaluation narratives (Blocks A-F), cover letter generation, "Why X?" form answers, interview STAR stories, and other tasks where writing quality and judgment matter.
2
+ description: Quality-sensitive worker on the low-cost DeepSeek V4 Flash OpenCode route by default. Use for offer evaluation narratives (Blocks A-F), cover letter generation, "Why X?" form answers, interview STAR stories, and other tasks where writing quality and judgment matter.
3
3
  role: quality
4
4
  targets:
5
- # No inline model: JobForge's models.yaml maps role "quality" to a
6
- # free OpenRouter model on OpenCode, while Claude/Codex keep their
7
- # quality-tier defaults from the standard preset. Claude Code reads
5
+ # No inline model: JobForge's models.yaml maps role "quality" to
6
+ # opencode-go/deepseek-v4-flash on OpenCode, while Claude/Codex keep
7
+ # their quality-tier defaults from the standard preset. Claude Code reads
8
8
  # .claude/iso-route.resolved.json; OpenCode reads opencode.json's
9
9
  # agent.quality.model (iso-harness 0.6.0+).
10
10
  opencode:
11
11
  mode: subagent
12
12
  temperature: 0.3
13
13
  reasoningEffort: medium
14
- # Primary (qwen/qwen3-next-80b-a3b-instruct:free) resolves from the
15
- # openrouter-free preset. First fallbacks intentionally avoid another
16
- # immediate hop through the same Venice/Qwen pool when OpenRouter
17
- # returns "[Venice] insufficient …" — gpt-oss-120b + nemotron are
18
- # usually different backends. Remaining picks stay free-only.
19
- fallback_models:
20
- - openrouter/openai/gpt-oss-120b:free
21
- - openrouter/nvidia/nemotron-3-super-120b-a12b:free
22
- - openrouter/z-ai/glm-4.5-air:free
23
- - openrouter/qwen/qwen3-coder:free
24
- - openrouter/google/gemma-4-31b-it:free
25
- - openrouter/meta-llama/llama-3.3-70b-instruct:free
26
14
  tools:
27
15
  geometra_connect: true
28
16
  geometra_page_model: true
@@ -40,10 +28,10 @@ targets:
40
28
 
41
29
  You are the @general-paid subagent. The orchestrator delegated this task to you because it requires quality writing or judgment — the kind of work `@general-free` isn't well-suited for.
42
30
 
43
- On OpenCode, this agent now defaults to a free OpenRouter model. On other
44
- harnesses, the same role may still resolve to a premium model. Your job is
45
- still the same: produce the best final writing you can from the context you
46
- were given.
31
+ On OpenCode, this agent defaults to DeepSeek V4 Flash so application work
32
+ does not fall back into overloaded free OpenRouter pools. On other harnesses,
33
+ the same role may still resolve to a premium model. Your job is still the
34
+ same: produce the best final writing you can from the context you were given.
47
35
 
48
36
  ## Do These Tasks
49
37
 
@@ -1,23 +1,16 @@
1
1
  ---
2
- description: Narrow-scope extractor on free-tier model. Use for single-purpose tasks where the orchestrator passes the exact input and expects a small, structured output — e.g., "extract these 8 fields from this JD text" or "parse this form schema into a label→type map". NOT for multi-step workflows.
2
+ description: Narrow-scope extractor on the low-cost DeepSeek V4 Flash OpenCode route. Use for single-purpose tasks where the orchestrator passes the exact input and expects a small, structured output — e.g., "extract these 8 fields from this JD text" or "parse this form schema into a label→type map". NOT for multi-step workflows.
3
3
  role: minimal
4
4
  targets:
5
- # No inline model: iso-route's "standard" preset maps role "minimal" to
6
- # each harness's smallest credible model. Claude Code reads
5
+ # No inline model: JobForge's models.yaml maps role "minimal" to each
6
+ # harness's smallest credible model. On OpenCode that is pinned to
7
+ # opencode-go/deepseek-v4-flash. Claude Code reads
7
8
  # .claude/iso-route.resolved.json; OpenCode reads opencode.json's
8
9
  # agent.minimal.model (iso-harness 0.6.0+).
9
10
  opencode:
10
11
  mode: subagent
11
12
  temperature: 0
12
13
  reasoningEffort: none
13
- # Primary (openai/gpt-oss-20b:free) resolves from openrouter-free
14
- # preset. Fallback chain sticks to small dense models with reliable
15
- # structured-output behavior — no creative generation upstream.
16
- fallback_models:
17
- - openrouter/google/gemma-4-26b-a4b-it:free
18
- - openrouter/nvidia/nemotron-nano-9b-v2:free
19
- - openrouter/google/gemma-4-31b-it:free
20
- - openrouter/z-ai/glm-4.5-air:free
21
14
  tools:
22
15
  geometra_*: false
23
16
  gmail_*: false
@@ -140,13 +140,18 @@ When the user says "apply to N jobs", "process the pipeline", or similar, execut
140
140
 
141
141
  ```
142
142
  Step 1 — Enumerate candidates
143
- - Grep data/applications/$(date +%Y-%m-%d).md and the last 3 day files for status "Evaluated"
143
+ - Grep data/applications/*.md for status "Evaluated" without loading every file into context
144
144
  - Also read data/pipeline.md for unprocessed URLs
145
145
  - Build ordered list: candidates = [job_1, job_2, ..., job_N]
146
146
 
147
147
  Step 2 — Dedup against already-applied
148
- - For each candidate, Grep data/pipeline.md + today's day file for "APPLIED" + company+role
149
- - Drop any match. Never re-apply.
148
+ - For each candidate, grep all four sources for URL and company+role:
149
+ data/pipeline.md, data/applications/*.md, batch/tracker-additions/*.tsv,
150
+ batch/tracker-additions/merged/*.tsv
151
+ - Drop any APPLIED / Applied match before counting toward N. Never re-apply.
152
+ - If a subagent later returns SKIP because it found a duplicate, treat that as
153
+ a missed preflight check; finish the current round, re-run dedupe, then pick
154
+ a replacement from the remaining candidates.
150
155
 
151
156
  Step 3 — Pre-flight cleanup (once, before the loop)
152
157
  - geometra_list_sessions()
package/iso/config.json CHANGED
@@ -1,49 +1,8 @@
1
1
  {
2
- "opencodeModelFallback": {
3
- "cooldown_seconds": 60,
4
- "timeout_seconds": 30,
5
- "notify_on_fallback": true,
6
- "fallback_models": [
7
- "openrouter/openai/gpt-oss-120b:free",
8
- "openrouter/z-ai/glm-4.5-air:free",
9
- "openrouter/nvidia/nemotron-3-super-120b-a12b:free",
10
- "openrouter/qwen/qwen3-next-80b-a3b-instruct:free"
11
- ],
12
- "retryable_error_patterns": [
13
- "\\bvenice\\b",
14
- "insufficient\\s+usd",
15
- "insufficient\\s+.*\\s+diem",
16
- "diem\\s+balance",
17
- "add\\s+credits",
18
- "chutes",
19
- "insufficient\\s+(?:credits?|funds?|balance)",
20
- "credit.*balance.*too.*low",
21
- "(?:temporarily\\s+)?unavailable|overloaded|try\\s+again"
22
- ]
23
- },
24
2
  "targets": {
25
3
  "opencode": {
26
- "plugin": ["@razroo/opencode-model-fallback"],
27
4
  "instructions": ["templates/states.yml"],
28
- "small_model": "openrouter/google/gemma-4-26b-a4b-it:free",
29
- "provider": {
30
- "openrouter": {
31
- "models": {
32
- "qwen/qwen3-coder:free": {},
33
- "z-ai/glm-4.5-air:free": {},
34
- "qwen/qwen3-next-80b-a3b-instruct:free": {},
35
- "openai/gpt-oss-20b:free": {},
36
- "openai/gpt-oss-120b:free": {},
37
- "minimax/minimax-m2.5:free": {},
38
- "nvidia/nemotron-3-super-120b-a12b:free": {},
39
- "nvidia/nemotron-3-nano-30b-a3b:free": {},
40
- "nvidia/nemotron-nano-9b-v2:free": {},
41
- "google/gemma-4-26b-a4b-it:free": {},
42
- "google/gemma-4-31b-it:free": {},
43
- "meta-llama/llama-3.3-70b-instruct:free": {}
44
- }
45
- }
46
- }
5
+ "small_model": "opencode-go/deepseek-v4-flash"
47
6
  }
48
7
  }
49
8
  }
@@ -5,9 +5,9 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
5
5
  ## Hard limits
6
6
 
7
7
  - [H1] Max 2 parallel `task` dispatches per message. For N jobs, run `ceil(N/2)` sequential rounds of 2. A round is not complete until both subagents return a final outcome (`APPLIED`, `APPLY FAILED`, `SKIP`, `Discarded`, or a written TSV path). A `task` tool result that only gives a session id / title is a launch acknowledgement, not completion. Applies in all modes, for all user phrasings ("urgent", "apply to 10 jobs now").
8
- why: higher parallelism blows through free-tier rate limits; each subagent requires post-cleanup and racing more than 2 reliably loses at least one result. On 2026-04-25 the orchestrator launched round 2 while round 1 had only returned task ids, leaving four application subagents in flight and losing two provider-fallback recoveries
8
+ why: each subagent requires post-cleanup and racing more than 2 reliably loses at least one result. On 2026-04-25 the orchestrator launched round 2 while round 1 had only returned task ids, leaving four application subagents in flight and losing two provider recoveries
9
9
 
10
- - [H2] Max 1 application per company+role. Before every `apply` dispatch, grep all four sources for the URL and for `company+role`: `data/pipeline.md`, all `data/applications/*.md` day files, `batch/tracker-additions/*.tsv`, `batch/tracker-additions/merged/*.tsv`. If any source shows APPLIED / Applied, skip the dispatch.
10
+ - [H2] Max 1 application per company+role. Before every `apply` dispatch, grep all four sources for the URL and for `company+role`: `data/pipeline.md`, all `data/applications/*.md` day files, `batch/tracker-additions/*.tsv`, `batch/tracker-additions/merged/*.tsv`. If any source shows APPLIED / Applied, skip the dispatch and pick a replacement from the remaining candidate list. Do not count duplicates toward a requested "apply to N jobs" total, and do not delegate obvious duplicates just so a subagent can return SKIP.
11
11
  why: 2026-04 same-day batch collision — when two batches target the same role, `npx job-forge merge` updates the existing day-file row rather than appending, so grepping day files alone misses earlier-batch applies; merged/*.tsv is the only place the breadcrumb remains
12
12
 
13
13
  - [H3] Before every batch of `task` dispatches that will use Geometra, call `geometra_list_sessions` then `geometra_disconnect({closeBrowser: true})`. Every round, no exceptions. Name this cleanup as an explicit "step 0" in your first-response plan for any multi-apply request — it is the most frequently skipped guardrail in practice, and skipping it produces cascade "Not connected" failures on the next dispatch.
@@ -37,7 +37,7 @@ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs v
37
37
  why: iso-trace showed 0.25% Agent calls across 5174 turns under a prior over-broad "delegate before 2nd tool call" rule — the rule was ignored in practice; narrowing matches the original cache-bust incident
38
38
 
39
39
  - [D2] Route subagent work by cost tier. `@general-free`: procedural — form-fill, TSV merge, verify, OTP retrieval, portal scan metadata extraction, one-shot structured-field transforms. `@general-paid`: quality-sensitive — offer evaluation narrative Blocks A-F, cover letters, "Why X?" answers, STAR interview stories, LinkedIn outreach. `@glm-minimal`: narrow ≤5K-input one-shot extract/classify jobs that do not need context.
40
- why: GLM 5.1 doesn't discount cache reads so procedural work there costs ~10×; free-tier models handle procedural work fine empirically (`opencode/big-pickle` processed 1000+ messages at $0)
40
+ why: OpenCode routes all JobForge tiers through DeepSeek V4 Flash by default now; recent traces showed free OpenRouter fallbacks freezing or hitting provider balance errors during applications
41
41
 
42
42
  - [D3] Read the active mode file before dispatch. Mode files own score gates, provider fallback, portal runbooks, and output shape.
43
43
  why: mode-specific rules change faster than global orchestration rules; keeping them out of the shared prefix preserves cache efficiency and prevents stale branches
package/models.yaml CHANGED
@@ -1,17 +1,15 @@
1
1
  # JobForge model policy.
2
2
  #
3
- # Extends @razroo/iso-route's bundled "openrouter-free" preset a
4
- # curated zero-cost routing config that keeps Claude Code / Codex on
5
- # their native providers but routes OpenCode through explicit free
6
- # OpenRouter model IDs. Preset content lives in
7
- # node_modules/@razroo/iso-route/presets/openrouter-free.yaml; run
8
- # `npx iso-route plan models.yaml` to see the resolved policy.
3
+ # Extends @razroo/iso-route's bundled "standard" preset, then pins every
4
+ # OpenCode route to DeepSeek V4 Flash. Recent traces showed free OpenRouter
5
+ # routes freezing or falling through Venice balance errors, so JobForge's
6
+ # OpenCode default is now "best affordable paid" rather than "free".
9
7
  #
10
8
  # JobForge's subagents bind to preset roles via the `role:` field in
11
9
  # iso/agents/<slug>.md:
12
- # @general-free → role: fast (Haiku / OpenCode big-pickle / gpt-5.4-mini)
13
- # @general-paid → role: quality (Opus 4.7 / OpenRouter Qwen3 Next 80B free / gpt-5.4)
14
- # @glm-minimal → role: minimal (Haiku / OpenRouter GPT-OSS-20B free / gpt-5.4-nano)
10
+ # @general-free → role: fast (Haiku / DeepSeek V4 Flash / gpt-5.4-mini)
11
+ # @general-paid → role: quality (Opus 4.7 / DeepSeek V4 Flash / gpt-5.4)
12
+ # @glm-minimal → role: minimal (Haiku / DeepSeek V4 Flash / gpt-5.4-nano)
15
13
  #
16
14
  # Override anything by adding fields here. For example, to pin Opus on
17
15
  # Claude Code for the @general-paid (quality) role:
@@ -29,11 +27,27 @@
29
27
  # provider: openai
30
28
  # model: gpt-5.4
31
29
 
32
- extends: openrouter-free
30
+ extends: standard
31
+
32
+ default:
33
+ targets:
34
+ opencode:
35
+ provider: opencode
36
+ model: opencode-go/deepseek-v4-flash
33
37
 
34
38
  roles:
35
39
  fast:
36
40
  targets:
37
41
  opencode:
38
42
  provider: opencode
39
- model: opencode/big-pickle
43
+ model: opencode-go/deepseek-v4-flash
44
+ quality:
45
+ targets:
46
+ opencode:
47
+ provider: opencode
48
+ model: opencode-go/deepseek-v4-flash
49
+ minimal:
50
+ targets:
51
+ opencode:
52
+ provider: opencode
53
+ model: opencode-go/deepseek-v4-flash
package/modes/apply.md CHANGED
@@ -17,7 +17,7 @@ Live application assistant. Reads the active application form in Chrome (via Geo
17
17
  why: prior aborted subagents leave Chromium sessions stuck in the pool; next `geometra_connect` fails with "Not connected" (see root `[H3]`)
18
18
 
19
19
  - [H5] Max 2 parallel `task` dispatches per round. For N jobs, run `ceil(N/2)` sequential rounds of 2. Never emit 3+ dispatches in a single message. Do not start the next round until both current-round subagents return final outcomes (`APPLIED`, `APPLY FAILED`, `SKIP`, `Discarded`, or a written TSV path); task/session ids are only launch receipts.
20
- why: free-tier rate limits + subagent post-cleanup cost; racing more than 2 reliably loses at least one result (see root `[H1]`). A 2026-04-25 OpenCode trace launched round 2 while round 1 was still running, then lost two fallback recoveries
20
+ why: subagent post-cleanup cost and portal state make racing more than 2 unreliable (see root `[H1]`). A 2026-04-25 OpenCode trace launched round 2 while round 1 was still running, then lost two provider recoveries
21
21
 
22
22
  ## Defaults
23
23
 
@@ -46,10 +46,10 @@ Live application assistant. Reads the active application form in Chrome (via Geo
46
46
  why: class-B Ashby / Cloudflare-fronted portals need a residential outbound IP; the fix is wired in Geometra MCP v1.59.0 but the orchestrator owns the config pipe. See "BYO Residential Proxy" in modes/reference-portals.md.
47
47
 
48
48
  - [D8] Upgrade application routing to `@general-paid` when the offer score is ≥ 4.0/5, the user flags "top-tier", "dream job", or "high-stakes", or the candidate is late-stage/post-screen.
49
- why: form-fill flows are 6+ steps; free-tier sometimes aborts mid-flow on large Greenhouse/Workday schemas; paid tier has more headroom
49
+ why: high-stakes applications need the quality-sensitive prompt and medium reasoning budget even though OpenCode now routes both application tiers through DeepSeek V4 Flash by default
50
50
 
51
- - [D9] If an upgraded `@general-paid` subagent fails with provider-side errors, re-dispatch the same URL once on `@general-free` before marking FAILED. Provider-side errors include Venice, Diem, Chutes, HTTP 402/429, insufficient credits/funds/balance, overload, and temporarily unavailable.
52
- why: OpenCode paid-tier routing can still use free OpenRouter model IDs; backend pool limits are not evidence that a procedural free-tier worker cannot complete the same form after preflight gates pass
51
+ - [D9] If a subagent fails with provider-side errors, do not auto-downgrade or re-dispatch the same URL. Report the provider failure, leave any TSV untouched unless there is a confirmed outcome, and inspect telemetry before retrying.
52
+ why: OpenCode now pins all JobForge application tiers to DeepSeek V4 Flash; switching `@general-paid` `@general-free` changes the prompt/tool budget but not the provider route, so automatic duplicate dispatches add risk without fixing provider availability
53
53
 
54
54
  ## Procedure
55
55
 
@@ -65,7 +65,7 @@ Live application assistant. Reads the active application form in Chrome (via Geo
65
65
  10. Generate answers from Block B + Block F + Section G + JD.
66
66
  11. Submit as ONE `run_actions` call [H1] using labels [D6] with `imeFriendly: true` [D4].
67
67
  12. On session error, run the 4-step recovery; only one retry [H2].
68
- 13. On upgraded-provider failure, downgrade once to `@general-free` [D9].
68
+ 13. On provider failure, stop and inspect telemetry before any retry [D9].
69
69
  14. On OTP prompt, fetch the code from Gmail via `gmail_get_message`.
70
70
  15. Submit the OTP with `geometra_fill_otp` and click Submit.
71
71
  16. Write outcome as `batch/tracker-additions/*.tsv` [H3].
@@ -98,7 +98,7 @@ Or, on failure:
98
98
  APPLY FAILED AFTER RECOVERY: <url>
99
99
  Error 1: <first error>
100
100
  Error 2: <post-recovery error>
101
- Recommend: re-dispatch on @general-paid
101
+ Recommend: inspect telemetry before retrying this URL
102
102
  ```
103
103
 
104
104
  ---
@@ -176,7 +176,10 @@ When `location_constraints` is absent, use the prose fields:
176
176
 
177
177
  ```
178
178
  Step 1 — Build the job list (N items)
179
- Step 2 — Dedup: Grep data/pipeline.md + today's day file for each company+role. Drop any already APPLIED.
179
+ Step 2 — Dedup: for each candidate, grep all four sources for the URL and for company+role:
180
+ data/pipeline.md, all data/applications/*.md day files,
181
+ batch/tracker-additions/*.tsv, batch/tracker-additions/merged/*.tsv.
182
+ Drop any already APPLIED before counting toward N; pick replacements from the remaining list.
180
183
  Step 3 — geometra_list_sessions() + geometra_disconnect({closeBrowser: true}) [once, before loop]
181
184
  Step 4 — For round in ceil(N/2):
182
185
  pair = jobs[round*2 : round*2 + 2]
@@ -192,7 +195,7 @@ Step 6 — Reconcile outcomes (Hard Limit #6):
192
195
  Step 7 — Summarize outcomes; do NOT auto-retry failures.
193
196
  ```
194
197
 
195
- 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.
198
+ 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.
196
199
 
197
200
  **Outcome routing (Hard Limit #6 in `AGENTS.md`):**
198
201
  - Subagents write `batch/tracker-additions/{num}-{slug}.tsv` — one TSV per job.
@@ -359,10 +362,10 @@ Call 4: geometra_run_actions({
359
362
  APPLY FAILED AFTER RECOVERY: <URL>
360
363
  Error 1: <first error message>
361
364
  Error 2: <error after recovery>
362
- Recommend: re-dispatch on @general-paid
365
+ Recommend: inspect telemetry before retrying this URL
363
366
  ```
364
367
 
365
- Do NOT try a third time. Do NOT try a different approach. The orchestrator will decide whether to re-dispatch on a bigger model.
368
+ Do NOT try a third time. Do NOT try a different approach. The orchestrator will decide whether to retry after inspecting telemetry.
366
369
 
367
370
  ### Skip schema re-fetches mid-flow (Rule D)
368
371
 
package/opencode.json CHANGED
@@ -1,33 +1,15 @@
1
1
  {
2
2
  "$schema": "https://opencode.ai/config.json",
3
- "model": "openrouter/qwen/qwen3-coder:free",
3
+ "model": "opencode-go/deepseek-v4-flash",
4
4
  "agent": {
5
5
  "fast": {
6
- "model": "opencode/big-pickle"
6
+ "model": "opencode-go/deepseek-v4-flash"
7
7
  },
8
8
  "quality": {
9
- "model": "openrouter/qwen/qwen3-next-80b-a3b-instruct:free"
9
+ "model": "opencode-go/deepseek-v4-flash"
10
10
  },
11
11
  "minimal": {
12
- "model": "openrouter/openai/gpt-oss-20b:free"
13
- }
14
- },
15
- "provider": {
16
- "openrouter": {
17
- "models": {
18
- "qwen/qwen3-coder:free": {},
19
- "z-ai/glm-4.5-air:free": {},
20
- "qwen/qwen3-next-80b-a3b-instruct:free": {},
21
- "openai/gpt-oss-20b:free": {},
22
- "openai/gpt-oss-120b:free": {},
23
- "minimax/minimax-m2.5:free": {},
24
- "nvidia/nemotron-3-super-120b-a12b:free": {},
25
- "nvidia/nemotron-3-nano-30b-a3b:free": {},
26
- "nvidia/nemotron-nano-9b-v2:free": {},
27
- "google/gemma-4-26b-a4b-it:free": {},
28
- "google/gemma-4-31b-it:free": {},
29
- "meta-llama/llama-3.3-70b-instruct:free": {}
30
- }
12
+ "model": "opencode-go/deepseek-v4-flash"
31
13
  }
32
14
  },
33
15
  "mcp": {
@@ -66,11 +48,8 @@
66
48
  }
67
49
  }
68
50
  },
69
- "plugin": [
70
- "@razroo/opencode-model-fallback"
71
- ],
72
51
  "instructions": [
73
52
  "templates/states.yml"
74
53
  ],
75
- "small_model": "openrouter/google/gemma-4-26b-a4b-it:free"
54
+ "small_model": "opencode-go/deepseek-v4-flash"
76
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job-forge",
3
- "version": "2.14.11",
3
+ "version": "2.14.13",
4
4
  "description": "AI-powered job search pipeline built on opencode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -97,7 +97,6 @@
97
97
  "@razroo/iso": "^0.2.5",
98
98
  "@razroo/iso-eval": "^0.4.0",
99
99
  "@razroo/iso-harness": "^0.6.1",
100
- "@razroo/iso-route": "^0.5.3",
101
- "@razroo/opencode-model-fallback": "^0.3.1"
100
+ "@razroo/iso-route": "^0.5.3"
102
101
  }
103
102
  }
@@ -21,9 +21,9 @@ const checks = [
21
21
  ["H7 distrusts subagent prose", () => every(files.instructions, ["must originate from a file", "not from prior subagent prose"])],
22
22
  ["shared prompt points to on-demand references", () => every(files.instructions, ["modes/{mode}.md", "modes/reference-setup.md", "modes/reference-portals.md", "modes/reference-geometra.md"])],
23
23
  ["apply mode owns high-stakes upgrade", () => every(files.apply, ["[D8]", "@general-paid", "4.0/5", "high-stakes"])],
24
- ["apply mode owns provider downgrade", () => every(files.apply, ["[D9]", "@general-free", "HTTP 402/429", "insufficient credits/funds/balance"])],
25
- ["models policy extends free OpenRouter preset", () => /extends:\s*openrouter-free/.test(files.models)],
26
- ["OpenCode fallback plugin is configured", () => every(files.config, ["opencodeModelFallback", "@razroo/opencode-model-fallback"])],
24
+ ["apply mode blocks provider auto-downgrade", () => every(files.apply, ["[D9]", "do not auto-downgrade", "inspect telemetry before retrying"])],
25
+ ["models policy pins OpenCode to DeepSeek V4 Flash", () => /extends:\s*standard/.test(files.models) && count(files.models, "opencode-go/deepseek-v4-flash") >= 4],
26
+ ["OpenCode fallback plugin is not configured", () => !every(files.config, ["opencodeModelFallback", "@razroo/opencode-model-fallback"])],
27
27
  ];
28
28
 
29
29
  const failures = checks
@@ -41,3 +41,7 @@ console.log(`JobForge iso smoke passed (${checks.length} checks).`);
41
41
  function every(source, needles) {
42
42
  return needles.every((needle) => source.includes(needle));
43
43
  }
44
+
45
+ function count(source, needle) {
46
+ return source.split(needle).length - 1;
47
+ }