nitm-ai-dev-toolkit 4.0.0 → 5.0.0

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.
Files changed (136) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/README.md +60 -4
  3. package/bin/cli.js +152 -25
  4. package/bin/factory-archive.js +71 -0
  5. package/bin/factory-prune.js +79 -0
  6. package/bin/factory-watch.js +509 -0
  7. package/package.json +1 -1
  8. package/src/monorepo/.agents/skills/factory/SKILL.md +593 -0
  9. package/src/monorepo/.claude/agents/admin-portal.agent.md +1 -1
  10. package/src/monorepo/.claude/agents/api-specialist.agent.md +1 -1
  11. package/src/monorepo/.claude/agents/backend-architect.agent.md +1 -1
  12. package/src/monorepo/.claude/agents/documenter.agent.md +1 -1
  13. package/src/monorepo/.claude/agents/factory-approver.agent.md +12 -0
  14. package/src/monorepo/.claude/agents/factory-builder.agent.md +12 -0
  15. package/src/monorepo/.claude/agents/factory-reviewer-code.agent.md +12 -0
  16. package/src/monorepo/.claude/agents/factory-reviewer-security.agent.md +12 -0
  17. package/src/monorepo/.claude/agents/factory-reviewer-ui.agent.md +12 -0
  18. package/src/monorepo/.claude/agents/factory-reviewer-ux.agent.md +12 -0
  19. package/src/monorepo/.claude/agents/factory-spec-writer.agent.md +12 -0
  20. package/src/monorepo/.claude/agents/feature-builder.agent.md +1 -1
  21. package/src/monorepo/.claude/agents/frontend-developer.agent.md +1 -1
  22. package/src/monorepo/.claude/agents/green.agent.md +1 -1
  23. package/src/monorepo/.claude/agents/implementer.agent.md +1 -1
  24. package/src/monorepo/.claude/agents/planner.agent.md +1 -1
  25. package/src/monorepo/.claude/agents/red.agent.md +1 -1
  26. package/src/monorepo/.claude/agents/refactor.agent.md +1 -1
  27. package/src/monorepo/.claude/agents/researcher.agent.md +1 -1
  28. package/src/monorepo/.claude/agents/reviewer.agent.md +1 -1
  29. package/src/monorepo/.claude/agents/tdd.agent.md +1 -1
  30. package/src/monorepo/.claude/agents-snippets/factory-approver.md +72 -0
  31. package/src/monorepo/.claude/agents-snippets/factory-builder.md +101 -0
  32. package/src/monorepo/.claude/agents-snippets/factory-reviewer-code.md +57 -0
  33. package/src/monorepo/.claude/agents-snippets/factory-reviewer-security.md +48 -0
  34. package/src/monorepo/.claude/agents-snippets/factory-reviewer-ui.md +64 -0
  35. package/src/monorepo/.claude/agents-snippets/factory-reviewer-ux.md +64 -0
  36. package/src/monorepo/.claude/agents-snippets/factory-spec-writer.md +119 -0
  37. package/src/monorepo/.claude/agents-snippets/feature-builder.md +1 -1
  38. package/src/monorepo/.claude/commands/factory.md +24 -0
  39. package/src/monorepo/.claude/prompt-snippets/assign-tasks.md +6 -2
  40. package/src/monorepo/.claude/prompt-snippets/bootstrap.md +22 -13
  41. package/src/monorepo/.claude/prompt-snippets/factory.md +20 -0
  42. package/src/monorepo/.claude/prompt-snippets/implement-feature.md +2 -2
  43. package/src/monorepo/.claude/prompt-snippets/tasks.md +49 -87
  44. package/src/monorepo/.claude/rules-snippets/documentation.md +8 -6
  45. package/src/monorepo/.cursor/agents/factory-approver.agent.md +11 -0
  46. package/src/monorepo/.cursor/agents/factory-builder.agent.md +11 -0
  47. package/src/monorepo/.cursor/agents/factory-reviewer-code.agent.md +11 -0
  48. package/src/monorepo/.cursor/agents/factory-reviewer-security.agent.md +11 -0
  49. package/src/monorepo/.cursor/agents/factory-reviewer-ui.agent.md +11 -0
  50. package/src/monorepo/.cursor/agents/factory-reviewer-ux.agent.md +11 -0
  51. package/src/monorepo/.cursor/agents/factory-spec-writer.agent.md +11 -0
  52. package/src/monorepo/.cursor/commands/factory.md +24 -0
  53. package/src/monorepo/.github/agents/factory-approver.agent.md +11 -0
  54. package/src/monorepo/.github/agents/factory-builder.agent.md +11 -0
  55. package/src/monorepo/.github/agents/factory-reviewer-code.agent.md +11 -0
  56. package/src/monorepo/.github/agents/factory-reviewer-security.agent.md +11 -0
  57. package/src/monorepo/.github/agents/factory-reviewer-ui.agent.md +11 -0
  58. package/src/monorepo/.github/agents/factory-reviewer-ux.agent.md +11 -0
  59. package/src/monorepo/.github/agents/factory-spec-writer.agent.md +11 -0
  60. package/src/monorepo/.github/prompts/factory.prompt.md +25 -0
  61. package/src/monorepo/.opencode/agents/factory-approver.md +20 -0
  62. package/src/monorepo/.opencode/agents/factory-builder.md +20 -0
  63. package/src/monorepo/.opencode/agents/factory-reviewer-code.md +20 -0
  64. package/src/monorepo/.opencode/agents/factory-reviewer-security.md +20 -0
  65. package/src/monorepo/.opencode/agents/factory-reviewer-ui.md +20 -0
  66. package/src/monorepo/.opencode/agents/factory-reviewer-ux.md +20 -0
  67. package/src/monorepo/.opencode/agents/factory-spec-writer.md +20 -0
  68. package/src/monorepo/.opencode/commands/factory.md +29 -0
  69. package/src/monorepo/AGENTS.md +22 -1
  70. package/src/monorepo/README.md +6 -0
  71. package/src/monorepo/factory/README.md +194 -0
  72. package/src/monorepo/factory/_gitignore +5 -0
  73. package/src/repo/.agents/skills/factory/SKILL.md +593 -0
  74. package/src/repo/.claude/agents/admin-portal.agent.md +1 -1
  75. package/src/repo/.claude/agents/api-specialist.agent.md +1 -1
  76. package/src/repo/.claude/agents/backend-architect.agent.md +1 -1
  77. package/src/repo/.claude/agents/documenter.agent.md +1 -1
  78. package/src/repo/.claude/agents/factory-approver.agent.md +12 -0
  79. package/src/repo/.claude/agents/factory-builder.agent.md +12 -0
  80. package/src/repo/.claude/agents/factory-reviewer-code.agent.md +12 -0
  81. package/src/repo/.claude/agents/factory-reviewer-security.agent.md +12 -0
  82. package/src/repo/.claude/agents/factory-reviewer-ui.agent.md +12 -0
  83. package/src/repo/.claude/agents/factory-reviewer-ux.agent.md +12 -0
  84. package/src/repo/.claude/agents/factory-spec-writer.agent.md +12 -0
  85. package/src/repo/.claude/agents/feature-builder.agent.md +1 -1
  86. package/src/repo/.claude/agents/frontend-developer.agent.md +1 -1
  87. package/src/repo/.claude/agents/green.agent.md +1 -1
  88. package/src/repo/.claude/agents/implementer.agent.md +1 -1
  89. package/src/repo/.claude/agents/planner.agent.md +1 -1
  90. package/src/repo/.claude/agents/red.agent.md +1 -1
  91. package/src/repo/.claude/agents/refactor.agent.md +1 -1
  92. package/src/repo/.claude/agents/researcher.agent.md +1 -1
  93. package/src/repo/.claude/agents/reviewer.agent.md +1 -1
  94. package/src/repo/.claude/agents/tdd.agent.md +1 -1
  95. package/src/repo/.claude/agents-snippets/factory-approver.md +72 -0
  96. package/src/repo/.claude/agents-snippets/factory-builder.md +100 -0
  97. package/src/repo/.claude/agents-snippets/factory-reviewer-code.md +57 -0
  98. package/src/repo/.claude/agents-snippets/factory-reviewer-security.md +48 -0
  99. package/src/repo/.claude/agents-snippets/factory-reviewer-ui.md +64 -0
  100. package/src/repo/.claude/agents-snippets/factory-reviewer-ux.md +64 -0
  101. package/src/repo/.claude/agents-snippets/factory-spec-writer.md +117 -0
  102. package/src/repo/.claude/agents-snippets/feature-builder.md +1 -1
  103. package/src/repo/.claude/commands/factory.md +24 -0
  104. package/src/repo/.claude/prompt-snippets/assign-tasks.md +6 -2
  105. package/src/repo/.claude/prompt-snippets/bootstrap.md +30 -20
  106. package/src/repo/.claude/prompt-snippets/factory.md +20 -0
  107. package/src/repo/.claude/prompt-snippets/implement-feature.md +2 -2
  108. package/src/repo/.claude/prompt-snippets/tasks.md +49 -112
  109. package/src/repo/.cursor/agents/factory-approver.agent.md +11 -0
  110. package/src/repo/.cursor/agents/factory-builder.agent.md +11 -0
  111. package/src/repo/.cursor/agents/factory-reviewer-code.agent.md +11 -0
  112. package/src/repo/.cursor/agents/factory-reviewer-security.agent.md +11 -0
  113. package/src/repo/.cursor/agents/factory-reviewer-ui.agent.md +11 -0
  114. package/src/repo/.cursor/agents/factory-reviewer-ux.agent.md +11 -0
  115. package/src/repo/.cursor/agents/factory-spec-writer.agent.md +11 -0
  116. package/src/repo/.cursor/commands/factory.md +24 -0
  117. package/src/repo/.github/agents/factory-approver.agent.md +11 -0
  118. package/src/repo/.github/agents/factory-builder.agent.md +11 -0
  119. package/src/repo/.github/agents/factory-reviewer-code.agent.md +11 -0
  120. package/src/repo/.github/agents/factory-reviewer-security.agent.md +11 -0
  121. package/src/repo/.github/agents/factory-reviewer-ui.agent.md +11 -0
  122. package/src/repo/.github/agents/factory-reviewer-ux.agent.md +11 -0
  123. package/src/repo/.github/agents/factory-spec-writer.agent.md +11 -0
  124. package/src/repo/.github/prompts/factory.prompt.md +25 -0
  125. package/src/repo/.opencode/agents/factory-approver.md +20 -0
  126. package/src/repo/.opencode/agents/factory-builder.md +20 -0
  127. package/src/repo/.opencode/agents/factory-reviewer-code.md +20 -0
  128. package/src/repo/.opencode/agents/factory-reviewer-security.md +20 -0
  129. package/src/repo/.opencode/agents/factory-reviewer-ui.md +20 -0
  130. package/src/repo/.opencode/agents/factory-reviewer-ux.md +20 -0
  131. package/src/repo/.opencode/agents/factory-spec-writer.md +20 -0
  132. package/src/repo/.opencode/commands/factory.md +29 -0
  133. package/src/repo/AGENTS.md +19 -1
  134. package/src/repo/README.md +6 -0
  135. package/src/repo/factory/README.md +194 -0
  136. package/src/repo/factory/_gitignore +5 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.0.0]
11
+
12
+ ### Breaking
13
+
14
+ - `/tasks` now writes a slim `tasks.md`: one checklist line per task (checkbox, ID, short description, optional `[P]`, optional ` — <ticket key>`). Details stay in `spec.md` and `plan.md`. `/assign-tasks` and `/implement-feature` read the slim lines and write ticket keys back after the em dash. Existing `tasks.md` files are not rewritten; re-run `/tasks` on a feature to convert one.
15
+ - `upgrade` no longer overwrites your files. It used to run `install --force`. It now adds missing files and keeps existing ones (`patch` semantics), then re-emits the bootstrap handoff. Overwriting needs an explicit `upgrade --force`, which prints a warning first. Scripts that relied on `upgrade` to refresh templates must add `--force`.
16
+
17
+ ### Added
18
+
19
+ - Factory pipeline (opt-in): `/factory <request>` takes a feature through intake, spec gate, plan gate, build, parallel lens reviews, approval, and local merge, with subcommands `start`, `approve`, `rework`, `status`, and `watch`. Nothing runs until `/factory` is invoked. Job state lives in `factory/jobs/`, which is git-ignored. Entry points ship for Claude Code, GitHub Copilot, Cursor, and OpenCode; Codex uses the orchestrator skill `.agents/skills/factory/SKILL.md`.
20
+ - Factory gates and intake: intake reads `AGENTS.md`, related features in `docs/features/`, and the code it touches, asks at most five glaring questions, and saves the answers to `intake.md`. Every job stops at `awaiting-spec` and `awaiting-plan` in every approval mode, and nothing is ticketed or built before the operator confirms the plan. Change requests are saved as `spec-<k>.md` or `plan-<k>.md`.
21
+ - Factory feature folders and tracking: each job belongs to `docs/features/<KEY>-<slug>/` (or `docs/features/<slug>/` before a ticket exists). `job.json` records `feature_dir`, `issue_key`, `size`, `tracking`, and `worktree`. Large work with a documented tracker creates tickets through `/assign-tasks`, writes their keys onto `tasks.md`, and moves them through In Progress, In Review, and Done. Done only follows `/factory approve`.
22
+ - Factory builds run in a git worktree on `factory/<id>`. `--in-place` builds in the main checkout instead, and only from a clean default branch with no other in-place job open. The builder writes `qa.md` with suggested checks, printed before approval; it never gates anything.
23
+ - Factory agents: `factory-spec-writer`, `factory-builder`, `factory-reviewer-code`, `factory-reviewer-security`, `factory-reviewer-ux`, `factory-reviewer-ui`, and `factory-approver`, with shared snippets in `.claude/agents-snippets/`. Their OpenCode wrappers use native V2 `permissions`.
24
+ - `factory/` template folder (README, `_gitignore`) installed by default. CLI `factory status` (one-shot table) and `factory watch` (live view) are read-only over `factory/jobs/*/job.json`; `abandoned` jobs are shown as closed.
25
+ - Factory `/factory archive [--all] [--dry-run]` moves closed jobs (`merged` or `abandoned`) from `factory/jobs/` to `factory/archive/`. By default only jobs last updated more than 7 days ago move. `--all` moves every closed job, and open jobs never move. CLI `factory archive` makes the same moves. `/factory archive` also asks before deleting an archived abandoned job's branch and worktree, and never pushes. Archived jobs are not resumable (`/factory start` says they are closed), and their ids count toward job numbering until a prune removes them.
26
+ - Factory `/factory prune [--all] [--abandoned] [--dry-run]` permanently deletes closed jobs from `factory/jobs/` and `factory/archive/`. By default only `merged` jobs are deleted, and only those last updated more than 7 days ago. `--all` drops the age limit for merged jobs. `--abandoned` also deletes `abandoned` jobs under the same age rule. Without it, abandoned jobs are kept and counted, and the flow offers to rerun with `--abandoned`. It lists the candidates, asks once in chat, and deletes only after a yes. When abandoned jobs are pruned, it then asks, per abandoned branch, whether to delete the branch and its worktree, as archive does. Open jobs and `docs/features/` are never touched. `/factory archive` keeps its selection: merged and abandoned. CLI `factory prune` lists by default and deletes with `--yes`; `--abandoned` is accepted only by `factory prune`.
27
+ - Link checks in `test/links.test.js`: every relative markdown link and `@path` import in `src/repo` and `src/monorepo` must resolve, and so must every scoped install (each `--env` × layout).
28
+
29
+ ### Changed
30
+
31
+ - Factory dashboard: `factory status` and `factory watch` hide closed jobs by default and print `N closed jobs hidden (--all to show)`. `--all` shows closed jobs and archived ones.
32
+ - Scoped installs (`--env`) also copy `.agents/skills/`, `.claude/agents-snippets/`, `.claude/prompt-snippets/`, `.claude/rules-snippets/`, and `factory/`, the shared references every harness's wrappers use.
33
+ - Templates named `_gitignore` install as `.gitignore`, because npm drops `.gitignore` files from packages.
34
+ - Releases publish through npm trusted publishing (OIDC, with provenance) from `.github/workflows/publish.yml`, which now also does the auto patch bump and tag (`auto-patch-tag.yml` removed). The `NPM_TOKEN` secret is no longer used.
35
+
36
+ ### Fixed
37
+
38
+ - Scoped Cursor, Copilot, and OpenCode installs no longer leave dangling links: `.claude/rules-snippets/` is now shared by every scope, since Cursor rules link there.
39
+ - Scoped installs share only `.agents/skills/`, so Claude/Codex ambient rules in `.agents/rules/` no longer appear in Cursor, Copilot, or OpenCode installs.
40
+ - `doctor` placeholder scan matches names with digits (for example `{{DB2_HOST}}`), skips `templates/` blank forms, and ignores runtime-only placeholders (`ISSUE_ID`, `ISSUE_KEY`, `FEATURE_NAME`, `NAME`), so a bootstrapped project can report clean.
41
+ - `.cursor/rules/orient-to-recent-work.mdc` skill link now resolves (`../` → `../../`).
42
+
43
+ ## [4.0.0]
44
+
10
45
  ### Migration
11
46
 
12
47
  - Toolkit v4 / package 4.0.0 requires native OpenCode V2 for OpenCode use; oh-my-opencode-slim >=3.0.0 requires OpenCode >=2.0.7. Native host config uses plural maps, ordered permissions, skill directories, and `mcp.servers`. Back up V1 (converted files are incompatible with it), preserve user/global settings and Slim's separate strict schema, leave the external starter unchanged, and activate on the next run/restart. Config `instructions` are ignored, ambient `AGENTS.md` works, `@refs` require explicit reads, and `request.body.temperature` is preserved but not currently sent by the runner. Preserve `lsp: true`; use project checks, not unavailable V2 LSP tools. Plugin/runtime integration is untested; publishing has not been executed.
@@ -118,5 +153,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
118
153
  - Documentation structure templates (`docs/features/`, `docs/fixes/`)
119
154
  - Monthly fix log template
120
155
 
121
- [Unreleased]: https://github.com/ninjasitm/ai-assisted-dev-toolkit/compare/v0.1.0...HEAD
156
+ [Unreleased]: https://github.com/ninjasitm/ai-assisted-dev-toolkit/compare/v4.0.0...HEAD
157
+ [4.0.0]: https://github.com/ninjasitm/ai-assisted-dev-toolkit/compare/v3.0.23...v4.0.0
122
158
  [0.1.0]: https://github.com/ninjasitm/ai-assisted-dev-toolkit/releases/tag/v0.1.0
package/README.md CHANGED
@@ -36,6 +36,7 @@ This repository serves as a **template source** for AI development instructions
36
36
  │ │ │ ├── rules/ # Agent behavior rules (ponytail, etc.)
37
37
  │ │ │ └── skills/ # Bundled skills (TDD, debugging, etc.)
38
38
  │ │ ├── docs/ # Documentation structure templates
39
+ │ │ ├── factory/ # Opt-in factory pipeline (README, .gitignore)
39
40
  │ │ ├── templates/ # Reusable document templates
40
41
  │ │ ├── AGENTS.md # AI agent context template
41
42
  │ │ └── README.md # Project README template
@@ -59,6 +60,7 @@ This repository serves as a **template source** for AI development instructions
59
60
  │ ├── apps/ # App-specific templates
60
61
  │ ├── packages/ # Package-specific templates
61
62
  │ ├── docs/ # Documentation structure templates
63
+ │ ├── factory/ # Opt-in factory pipeline (README, .gitignore)
62
64
  │ ├── templates/ # Reusable document templates
63
65
  │ └── AGENTS.md # Monorepo agent context
64
66
  │
@@ -172,9 +174,12 @@ npx nitm-ai-dev-toolkit patch
172
174
  # Inspect the install: missing files and unresolved placeholders
173
175
  npx nitm-ai-dev-toolkit doctor
174
176
 
175
- # Upgrade to the latest published version
177
+ # Upgrade to the latest published version: adds missing files and keeps your existing ones
176
178
  npx nitm-ai-dev-toolkit upgrade
177
179
 
180
+ # Also overwrite existing files with the latest templates (back up customisations first)
181
+ npx nitm-ai-dev-toolkit upgrade --force
182
+
178
183
  # Scaffold the oh-my-opencode-slim starter (delegates to `npx nitm-opencode-starter install`)
179
184
  npx nitm-ai-dev-toolkit omo-slim-starter install
180
185
  ```
@@ -214,7 +219,7 @@ Follow official V2 [migration](https://opencode.ai/v2/docs/migrate-v1), [config]
214
219
 
215
220
  ### Native Codex
216
221
 
217
- - `--env codex` includes `.codex/`, `.agents/`, and shared context, plus `.github/instructions/` and `.claude/rules-snippets/` solely to supply the standards linked from `AGENTS.md` and those standards' wrapper source content. It does not install other harness command, agent, or config directories. These reference files neither establish Copilot/Claude use nor load automatically as Codex rules; other harness scopes are unchanged.
222
+ - `--env codex` includes `.codex/`, `.agents/`, and shared context, plus `.github/instructions/` solely to supply the standards linked from `AGENTS.md`. The shared `.claude/rules-snippets/` standards are installed in every scope, so those standards' wrapper source content is present too. It does not install other harness command, agent, or config directories. These reference files neither establish Copilot/Claude use nor load automatically as Codex rules; other harness scopes are unchanged.
218
223
  - Codex reads `AGENTS.md` and discovers `.agents/skills/*/SKILL.md` natively. Monorepos can use more-specific nested `AGENTS.md` files for app/package guidance. Linked standards are references to read as needed, not automatically loaded Claude/Copilot rules.
219
224
  - The optional `.codex/config.toml` is deliberately comment-only: it preserves the user's inherited defaults. Codex loads project config only after the user explicitly trusts the project; the toolkit does not grant trust or set model/provider, approval, sandbox, security, or feature overrides.
220
225
  - For manual copies without `.nitm/`, provide `.claude/prompt-snippets/bootstrap.md` to Codex directly. For incremental AI-guided merges, provide `.claude/prompt-snippets/bootstrap-patch.md` as a task document; the CLI `patch` command only adds missing files.
@@ -238,6 +243,7 @@ Templates use `{{PLACEHOLDER}}` syntax for values you need to customize. The boo
238
243
  | `{{PACKAGE_MANAGER}}` | Package manager | `npm`, `composer`, `dotnet`, `pip`, `bundler`, `go`, `cargo`, `maven` |
239
244
  | `{{FILE_EXTENSION}}` | File extension | `ts`, `php`, `cs`, `py`, `rb`, `go`, `rs`, `java` |
240
245
  | `{{DEV_PORT}}` | Development server port | `3000`, `8000`, `5000` |
246
+ | `{{DEV_PORT_FLAG}}` | Flag or env var that sets the dev port | `--port 3000`, `PORT=3000` |
241
247
  | `{{DEPLOY_PLATFORM}}` | Deployment target | `Vercel`, `AWS`, `Azure`, `Heroku` |
242
248
  | `{{DATABASE}}` | Database/ORM | `Prisma`, `Eloquent`, `EF Core`, `SQLAlchemy`, `ActiveRecord` |
243
249
  | `{{TEST_FRAMEWORK}}` | Testing framework | `Vitest`, `PHPUnit`, `xUnit`, `pytest`, `RSpec`, `JUnit` |
@@ -260,6 +266,7 @@ Templates use `{{PLACEHOLDER}}` syntax for values you need to customize. The boo
260
266
  | Placeholder | Description | Example |
261
267
  | --------------------- | -------------------- | --------------------------------------------------- |
262
268
  | `{{INSTALL_COMMAND}}` | Install dependencies | `npm install`, `composer install`, `dotnet restore` |
269
+ | `{{SETUP_COMMAND}}` | Install command | `npm install`, `composer install`, `dotnet restore` |
263
270
  | `{{DEV_COMMAND}}` | Run dev server | `npm run dev`, `php artisan serve`, `dotnet watch` |
264
271
  | `{{BUILD_COMMAND}}` | Build for production | `npm run build`, `dotnet publish` |
265
272
  | `{{TEST_COMMAND}}` | Run tests | `npm test`, `./vendor/bin/phpunit`, `dotnet test` |
@@ -291,6 +298,17 @@ Templates use `{{PLACEHOLDER}}` syntax for values you need to customize. The boo
291
298
  | `{{APP_3}}` | Third app name | `web`, `marketing`, `landing` |
292
299
  | `{{PROJECT_STRUCTURE}}` | Full project structure | (Generated tree diagram) |
293
300
 
301
+ ### Factory Placeholders
302
+
303
+ Used by the opt-in factory (`.agents/skills/factory/SKILL.md` and `.claude/agents-snippets/factory-*.md`).
304
+
305
+ | Placeholder | Description | Example |
306
+ | --------------------------- | ------------------------------------------------------------------------------------------- | ------------------------ |
307
+ | `{{DEFAULT_BRANCH}}` | Branch factory jobs start from and merge into | `main`, `master` |
308
+ | `{{FACTORY_PORT_BASE}}` | Base dev-server port. Job `n` uses `base + 10n` for the builder, `+1` for ux, and `+2` for ui | `5100` |
309
+ | `{{APPROVAL_MODE}}` | `human` stops at `awaiting-human`; `auto` merges on approval | `human`, `auto` |
310
+ | `{{FACTORY_REVIEW_LENSES}}` | Review lenses to run. `ux` and `ui` need browser tooling | `code, security` |
311
+
294
312
  ## Supported Ecosystems
295
313
 
296
314
  The bootstrap command supports the following language ecosystems:
@@ -616,6 +634,43 @@ The bundled skills complement custom agents:
616
634
  - **`writing-plans`** — Creating the plans that Feature Builder and TDD agents execute
617
635
  - **`test-driven-development`** — The TDD methodology that Red/Green/Refactor agents implement
618
636
 
637
+ ## Factory (opt-in, v5)
638
+
639
+ The factory is an opt-in pipeline for feature-sized work. One orchestrator takes a feature through spec → plan gate → build → review → approve → merge. Each job belongs to a feature folder, `docs/features/<KEY>-<slug>/` (or `docs/features/<slug>/` before a ticket exists), which holds `spec.md` and, for large work, a slim `tasks.md`. Job state lives under `factory/jobs/<NNN-slug>/`, and each job builds in its own git worktree on branch `factory/<id>` (or in place on the job's branch, when you ask for `--in-place`).
640
+
641
+ It runs alongside Feature Builder and TDD and does not replace them. Nothing runs until you invoke `/factory`. Quick fixes stay direct.
642
+
643
+ - **Agents:** `factory-spec-writer`, `factory-builder`, `factory-reviewer-code`, `factory-reviewer-security`, `factory-reviewer-ux`, `factory-reviewer-ui`, and `factory-approver`. Wrappers ship for Claude Code, GitHub Copilot, Cursor, and OpenCode. Codex uses the orchestrator skill only.
644
+ - **Setup and reference:** [`src/repo/factory/README.md`](src/repo/factory/README.md) (the monorepo copy is in `src/monorepo/factory/`). The installer copies `factory/` into your project.
645
+
646
+ | Command | What it does |
647
+ | --- | --- |
648
+ | `/factory <feature request>` | Intake: reads the project and the features it touches, asks only the glaring questions, then asks you to continue or change the answers. Nothing is created before you continue |
649
+ | `/factory start <KEY \| slug \| job-id>` | Pick up an existing feature folder by ticket key or slug, or resume its open job (for example one waiting at a gate). Add `--in-place` only for a new job |
650
+ | `/factory approve <job-id>` | Merge a job waiting for you (`awaiting-human` or `needs-human`); on a `merged` job, move its tickets to Done |
651
+ | `/factory rework <job-id> <note>` | Restart the builder with your note and a fresh retry budget |
652
+ | `/factory status [--all]` | Summarize open jobs (read-only). Closed jobs are hidden unless `--all` |
653
+ | `/factory watch [--all]` | Show the status table here, then print the command to run the live view in your terminal (read-only). Closed jobs are hidden unless `--all` |
654
+ | `/factory archive [--all] [--dry-run]` | Move closed jobs (`merged` or `abandoned`) from `factory/jobs/` to `factory/archive/`. By default only jobs not updated for 7 days move. `--dry-run` moves nothing. Open jobs never move |
655
+ | `/factory prune [--all] [--abandoned] [--dry-run]` | Permanently delete closed jobs from `factory/jobs/` and `factory/archive/`, after one confirmation. By default only `merged` jobs (same 7-day age rule as archive; `--all` drops the age limit). `--abandoned` also deletes `abandoned` jobs. Open jobs and `docs/features/` are never touched |
656
+
657
+ **Two gates are mandatory.** Every job stops at `awaiting-spec` after the spec, and at `awaiting-plan` after the plan. At the spec gate you confirm the spec or request changes. At the plan gate you review the tasks, the size, the tracking proposal, and the build mode, then confirm or request changes. Leave either gate for later with `/factory start <job-id>`. Nothing is ticketed or built before you confirm the plan.
658
+
659
+ **The builder also writes `qa.md`.** It lists how to run the feature, numbered manual checks tied to the acceptance criteria, and what the automated tests do not cover. The orchestrator shows it before approval as suggested checks. It is not a gate.
660
+
661
+ **The approval gate is on by default.** With `{{APPROVAL_MODE}}` set to `human`, an approved job stops at `awaiting-human` until you run `/factory approve <job-id>`. Set `{{APPROVAL_MODE}}` to `auto` to merge on approval instead. The approver still escalates changes to auth, payments, migrations, and similar high-risk areas. Merges are local, and the factory never pushes.
662
+
663
+ Check jobs from a terminal:
664
+
665
+ ```bash
666
+ npx nitm-ai-dev-toolkit factory status [--all] # one-shot table of open jobs (--all: closed too)
667
+ npx nitm-ai-dev-toolkit factory watch [--all] # live view: ↑/↓ to select, Enter to open, q to quit
668
+ npx nitm-ai-dev-toolkit factory archive [--all] [--dry-run] # move closed jobs to factory/archive/
669
+ npx nitm-ai-dev-toolkit factory prune [--all] [--abandoned] [--dry-run] # list merged jobs to delete; add --yes to delete them
670
+ ```
671
+
672
+ `status` and `watch` read `factory/jobs/*/job.json`, write nothing, and hide closed jobs unless `--all` is passed. `archive` moves closed jobs and touches no git state. `prune` permanently deletes merged jobs from both folders, after you confirm. Pass `--abandoned` to delete abandoned jobs too. Neither touches open jobs or `docs/features/`. The factory's placeholders are listed under [Factory Placeholders](#factory-placeholders).
673
+
619
674
  ## AI Agent Skills
620
675
 
621
676
  Enhance your AI agents with specialized capabilities using the **Agent Skills** open format. Skills are folders of instructions, scripts, and resources that agents can discover and use to perform tasks more accurately and efficiently.
@@ -693,7 +748,7 @@ Skills are installed to `{.cursor|.agents|.github}/skills/` and automatically av
693
748
 
694
749
  ### Bundled Universal Skills
695
750
 
696
- This toolkit includes **32 pre-installed universal skills** in the `.agents/skills/` folder. These skills are copied automatically when you set up your project and require **no additional installation**.
751
+ This toolkit includes **33 pre-installed universal skills** in the `.agents/skills/` folder. These skills are copied automatically when you set up your project and require **no additional installation**.
697
752
 
698
753
  | Skill | Description |
699
754
  | ---------------------------------- | ---------------------------------------------------------------------------------------------------- |
@@ -702,6 +757,7 @@ This toolkit includes **32 pre-installed universal skills** in the `.agents/skil
702
757
  | **verification-before-completion** | Quality checks before claiming work is done. Evidence before assertions, always. |
703
758
  | **writing-plans** | Creating clear implementation plans before coding. |
704
759
  | **executing-plans** | Following through on implementation plans systematically. |
760
+ | **factory** | Opt-in spec-to-merge feature pipeline with review lenses and an approval gate. |
705
761
  | **requesting-code-review** | Best practices for requesting code reviews from teammates. |
706
762
  | **receiving-code-review** | Responding constructively to review feedback. |
707
763
  | **brainstorming** | Structured ideation and problem-solving sessions. |
@@ -893,7 +949,7 @@ After copying templates and installing additional skills, your project will have
893
949
  │ └── SKILL.md
894
950
  ├── verification-before-completion/
895
951
  │ └── SKILL.md
896
- └── ... (32 skills total)
952
+ └── ... (33 skills total)
897
953
 
898
954
  .cursor/ # or .github/ for GitHub Copilot
899
955
  ├── rules/ # IDE behavior rules (from this toolkit)
package/bin/cli.js CHANGED
@@ -9,6 +9,9 @@
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
11
  const { execSync, spawnSync } = require('child_process');
12
+ const factory = require('./factory-watch.js');
13
+ const factoryArchive = require('./factory-archive.js');
14
+ const factoryPrune = require('./factory-prune.js');
12
15
 
13
16
  const PKG_ROOT = path.join(__dirname, '..');
14
17
  const SRC_DIR = path.join(PKG_ROOT, 'src');
@@ -16,13 +19,13 @@ const SRC_DIR = path.join(PKG_ROOT, 'src');
16
19
  // Platform config dirs per harness. Claude and Codex share `.agents` (universal skills).
17
20
  const ENV_DIRS = {
18
21
  claude: ['.claude', '.agents'],
19
- codex: ['.codex', '.agents', path.join('.github', 'instructions'), path.join('.claude', 'rules-snippets')],
22
+ codex: ['.codex', '.agents', path.join('.github', 'instructions')],
20
23
  copilot: ['.github', '.vscode'],
21
24
  cursor: ['.cursor'],
22
25
  // Shared reference sources, not other harnesses' active config/agent wrappers.
23
26
  opencode: ['.opencode', path.join('.agents', 'skills'),
24
27
  path.join('.claude', 'agents-snippets'), path.join('.claude', 'prompt-snippets'),
25
- path.join('.claude', 'rules-snippets'), path.join('.github', 'instructions')],
28
+ path.join('.github', 'instructions')],
26
29
  };
27
30
  const ENV_INFO = {
28
31
  claude: { name: 'Claude Code', command: '/bootstrap (Claude Code command in .claude/commands/)' },
@@ -33,8 +36,17 @@ const ENV_INFO = {
33
36
  };
34
37
  // Files/dirs copied for every environment (shared across harnesses).
35
38
  const SHARED_ROOT = ['AGENTS.md', 'README.md', 'CLAUDE.md', '.mcp.json'];
36
- const SHARED_DIRS = ['docs', 'templates', 'hooks'];
37
-
39
+ // `factory` is the job queue every harness reads and writes, so it is shared.
40
+ // Agent/skill bodies and prompt snippets are referenced by wrappers in every harness
41
+ // (e.g. `.claude/agents-snippets/*` from Cursor and Copilot agents), so they are shared too.
42
+ // `.claude/rules-snippets` is linked the same way from every harness's rules and instructions.
43
+ // Only `.agents/skills` is shared: `.agents/rules` is Claude/Codex ambient config.
44
+ const SHARED_DIRS = ['docs', 'templates', 'hooks', 'factory', path.join('.agents', 'skills'),
45
+ path.join('.claude', 'agents-snippets'), path.join('.claude', 'prompt-snippets'),
46
+ path.join('.claude', 'rules-snippets')];
47
+ // npm drops `.gitignore` files from packages, so templates ship them as `_gitignore`
48
+ // and install them under the real name.
49
+ const TEMPLATE_RENAMES = { '_gitignore': '.gitignore' };
38
50
  const C = {
39
51
  reset: '\x1b[0m',
40
52
  cyan: '\x1b[36m',
@@ -85,6 +97,12 @@ function templateFiles(type) {
85
97
  return files;
86
98
  }
87
99
 
100
+ // Path a template file is installed to (see TEMPLATE_RENAMES).
101
+ function installName(rel) {
102
+ const renamed = TEMPLATE_RENAMES[path.basename(rel)];
103
+ return renamed ? path.join(path.dirname(rel), renamed) : rel;
104
+ }
105
+
88
106
  // Restrict the file set to the chosen environment (shared + that env's dirs).
89
107
  function scopeFiles(files, env) {
90
108
  if (!env || !ENV_DIRS[env]) return files;
@@ -105,7 +123,8 @@ function copyMissing(type, cwd, force, env) {
105
123
  let skipped = 0;
106
124
  for (const rel of files) {
107
125
  const from = path.join(src, rel);
108
- const to = path.join(cwd, rel);
126
+ const dest = installName(rel);
127
+ const to = path.join(cwd, dest);
109
128
  if (fs.existsSync(to) && !force) {
110
129
  skipped++;
111
130
  continue;
@@ -130,7 +149,8 @@ function conflicts(type, cwd, env) {
130
149
  const files = scopeFiles(templateFiles(type), env);
131
150
  const out = [];
132
151
  for (const rel of files) {
133
- if (fs.existsSync(path.join(cwd, rel))) out.push(rel);
152
+ const dest = installName(rel);
153
+ if (fs.existsSync(path.join(cwd, dest))) out.push(dest);
134
154
  }
135
155
  return out;
136
156
  }
@@ -219,14 +239,25 @@ function cmdPatch(cwd, flags) {
219
239
  return { copied, skipped };
220
240
  }
221
241
 
242
+ // Placeholders the agent fills per task or feature at runtime, so they are not bootstrap leftovers.
243
+ const RUNTIME_PLACEHOLDERS = new Set(['ISSUE_ID', 'ISSUE_KEY', 'FEATURE_NAME', 'NAME']);
244
+ // `templates/` ships blank forms whose placeholders are meant to stay until a feature is written.
245
+ const PLACEHOLDER_EXEMPT_DIR = 'templates';
246
+
247
+ function hasUnresolvedPlaceholder(content) {
248
+ const names = content.match(/\{\{[A-Z][A-Z0-9_]*\}\}/g) || [];
249
+ return names.some((name) => !RUNTIME_PLACEHOLDERS.has(name.slice(2, -2)));
250
+ }
251
+
222
252
  function scanPlaceholders(cwd, relFiles) {
223
253
  const found = [];
224
254
  for (const rel of relFiles) {
255
+ if (path.normalize(rel).split(path.sep)[0] === PLACEHOLDER_EXEMPT_DIR) continue;
225
256
  const p = path.join(cwd, rel);
226
257
  if (!fs.existsSync(p)) continue;
227
258
  try {
228
259
  const content = fs.readFileSync(p, 'utf8');
229
- if (/\{\{[A-Z_]+\}\}/.test(content)) found.push(rel);
260
+ if (hasUnresolvedPlaceholder(content)) found.push(rel);
230
261
  } catch (_) { /* ignore */ }
231
262
  }
232
263
  return found;
@@ -234,7 +265,7 @@ function scanPlaceholders(cwd, relFiles) {
234
265
 
235
266
  function cmdDoctor(cwd, flags) {
236
267
  const type = detectType(cwd, flags);
237
- const files = scopeFiles(templateFiles(type), flags.env);
268
+ const files = scopeFiles(templateFiles(type), flags.env).map(installName);
238
269
  const missingFiles = [];
239
270
  let present = 0;
240
271
  for (const rel of files) {
@@ -261,6 +292,16 @@ function cmdDoctor(cwd, flags) {
261
292
  return result;
262
293
  }
263
294
 
295
+ // Upgrade runs the latest package in patch mode: missing files are added and existing
296
+ // (possibly customised) files are kept. Only an explicit --force reaches `install --force`.
297
+ function upgradeArgs(flags) {
298
+ const force = Boolean(flags && flags.force);
299
+ const args = ['--yes', 'nitm-ai-dev-toolkit@latest', force ? 'install' : 'patch'];
300
+ if (force) args.push('--force');
301
+ if (flags && flags.env) args.push('--env', flags.env);
302
+ return args;
303
+ }
304
+
264
305
  function cmdUpgrade(cwd, flags) {
265
306
  let latest;
266
307
  try {
@@ -275,17 +316,17 @@ function cmdUpgrade(cwd, flags) {
275
316
  } catch (_) {
276
317
  cur = 'unknown';
277
318
  }
278
- if (latest === cur) {
279
- ok(`Already on latest version (${cur}).`);
280
- return;
319
+ if (latest === cur) info(`Already on latest version (${cur}); refreshing missing files and the handoff.`);
320
+ else info(`New version available: ${latest} (current ${cur}).`);
321
+ if (flags && flags.force) {
322
+ warn('--force: existing files will be overwritten with the latest templates. Back up your customisations first.');
323
+ } else {
324
+ info('Adding missing files only; your existing files are kept. Pass --force to overwrite them.');
281
325
  }
282
- info(`New version available: ${latest} (current ${cur}).`);
283
326
  info('Upgrading via latest package...');
284
- const args = ['--yes', 'nitm-ai-dev-toolkit@latest', 'install', '--force'];
285
- if (flags && flags.env) args.push('--env', flags.env);
286
- const r = spawnSync('npx', args, { cwd, stdio: 'inherit', timeout: 120000 });
327
+ const r = spawnSync('npx', upgradeArgs(flags), { cwd, stdio: 'inherit', timeout: 120000 });
287
328
  if (r.error) { err('Upgrade failed: ' + r.error.message); return; }
288
- if (r.status === 0) ok('Upgrade complete.');
329
+ if (r.status === 0) ok('Upgrade complete. Hand off to your AI agent with .nitm/BOOTSTRAP.md to finish setup.');
289
330
  else err('Upgrade failed.');
290
331
  }
291
332
 
@@ -306,6 +347,38 @@ function cmdOmoStarter(cwd, flags) {
306
347
  }
307
348
  }
308
349
 
350
+ const FACTORY_USAGE = 'Usage: nitm-ai-dev-toolkit factory <status|watch|archive|prune> [--dir <path>] [--all] [--abandoned] [--interval <ms>] [--dry-run] [--yes]';
351
+ const FACTORY_SUB_USAGE = {
352
+ archive: 'Usage: nitm-ai-dev-toolkit factory archive [--dir <path>] [--all] [--dry-run]',
353
+ prune: 'Usage: nitm-ai-dev-toolkit factory prune [--dir <path>] [--all] [--abandoned] [--dry-run] [--yes]',
354
+ };
355
+
356
+ function cmdFactory(cwd, flags) {
357
+ const sub = flags._[1];
358
+ // --abandoned changes what prune deletes; elsewhere it would be a silent no-op, so refuse it.
359
+ if (flags.abandoned && sub !== 'prune') {
360
+ err('--abandoned is only for factory prune');
361
+ process.exitCode = 1;
362
+ return;
363
+ }
364
+ if (sub === 'status') return factory.cmdFactoryStatus(cwd, flags);
365
+ if (sub === 'watch') return factory.cmdFactoryWatch(cwd, flags);
366
+ if (sub === 'archive' || sub === 'prune') {
367
+ const extra = flags._.slice(2);
368
+ if (extra.length) {
369
+ err(`Unknown option for factory ${sub}: ${extra[0]}`);
370
+ err(FACTORY_SUB_USAGE[sub]);
371
+ process.exitCode = 1;
372
+ return;
373
+ }
374
+ return sub === 'archive'
375
+ ? factoryArchive.cmdFactoryArchive(cwd, flags)
376
+ : factoryPrune.cmdFactoryPrune(cwd, flags);
377
+ }
378
+ err(FACTORY_USAGE);
379
+ process.exitCode = 1;
380
+ }
381
+
309
382
  function parseFlags(argv) {
310
383
  const flags = { _: [] };
311
384
  for (let i = 0; i < argv.length; i++) {
@@ -313,7 +386,21 @@ function parseFlags(argv) {
313
386
  if (a === '--monorepo') flags.monorepo = true;
314
387
  else if (a === '--repo') flags.repo = true;
315
388
  else if (a === '--force' || a === '-f') flags.force = true;
389
+ else if (a === '--all') flags.all = true;
390
+ else if (a === '--dry-run') flags.dryRun = true;
391
+ else if (a === '--yes') flags.yes = true;
392
+ else if (a === '--abandoned') flags.abandoned = true;
316
393
  else if (a === '--help' || a === '-h') flags.help = true;
394
+ else if (a === '--dir' || a === '--interval') {
395
+ const v = argv[++i];
396
+ if (v === undefined || v.startsWith('--')) throw new Error(`${a} requires a value`);
397
+ flags[a.slice(2)] = v;
398
+ } else if (a.startsWith('--dir=') || a.startsWith('--interval=')) {
399
+ const eq = a.indexOf('=');
400
+ const v = a.slice(eq + 1);
401
+ if (!v) throw new Error(`${a.slice(0, eq)}= requires a value`);
402
+ flags[a.slice(2, eq)] = v;
403
+ }
317
404
  else if (a === '--env') {
318
405
  const v = argv[++i];
319
406
  if (v === undefined || v.startsWith('--')) throw new Error('--env requires a value (e.g. --env claude)');
@@ -341,8 +428,10 @@ Usage:
341
428
  Add only missing template files (never overwrites existing ones), then
342
429
  re-emit the bootstrap handoff so your AI agent can finish configuration.
343
430
 
344
- npx nitm-ai-dev-toolkit upgrade
345
- Upgrade to the latest published version (re-scaffolds via @latest).
431
+ npx nitm-ai-dev-toolkit upgrade [--env <env>] [--force]
432
+ Upgrade to the latest published version. Like \`patch\`, adds missing files and
433
+ keeps your existing ones, then re-emits the bootstrap handoff. --force also
434
+ overwrites existing files with the latest templates (back up customisations first).
346
435
 
347
436
  npx nitm-ai-dev-toolkit doctor [--monorepo|--repo] [--env <env>]
348
437
  Inspect the current install: missing files, unresolved {{PLACEHOLDER}}
@@ -352,6 +441,31 @@ Usage:
352
441
  Run \`npx nitm-opencode-starter install\` to scaffold the oh-my-opencode-slim
353
442
  starter (published to npm). Falls back to printing the command if unavailable.
354
443
 
444
+ npx nitm-ai-dev-toolkit factory status [--dir <path>] [--all]
445
+ Print one table of factory jobs (factory/jobs/<NNN-slug>/job.json), then exit.
446
+ Closed jobs (merged, abandoned) are hidden and counted; --all shows them,
447
+ plus archived jobs from factory/archive/ (marked [archived]).
448
+ Read-only. Run /factory <feature> in your agent to create jobs.
449
+
450
+ npx nitm-ai-dev-toolkit factory watch [--dir <path>] [--interval <ms>] [--all]
451
+ Live terminal view of factory jobs. ↑/↓ or j/k select, Enter opens a job's
452
+ markdown files, q quits. Closed jobs are hidden unless --all.
453
+ Not a TTY? Behaves like \`factory status\`.
454
+
455
+ npx nitm-ai-dev-toolkit factory archive [--dir <path>] [--all] [--dry-run]
456
+ Move closed jobs (merged, abandoned) from factory/jobs/ to factory/archive/.
457
+ Default: only jobs closed more than 7 days ago. --all: every closed job.
458
+ --dry-run: list what would move, move nothing. Open jobs are never touched.
459
+ Does not touch git or branches (/factory archive and /factory prune handle those).
460
+
461
+ npx nitm-ai-dev-toolkit factory prune [--dir <path>] [--all] [--abandoned] [--dry-run] [--yes]
462
+ PERMANENTLY delete closed jobs from factory/jobs/ and factory/archive/.
463
+ Default: only merged jobs closed more than 7 days ago. --all: every merged job
464
+ (no age limit). --abandoned: also delete abandoned jobs (same age rule).
465
+ Without --yes it only lists what would be deleted. --dry-run always only
466
+ lists, even with --yes. Open jobs are never touched. Deletes job folders only;
467
+ git, docs and branches are not touched.
468
+
355
469
  Options:
356
470
  --env <env> Optional. Limit the scaffold to ONE harness:
357
471
  claude | codex | copilot | cursor | opencode
@@ -361,26 +475,36 @@ Options:
361
475
  file directly (no native /bootstrap command is provided).
362
476
  --monorepo Force monorepo template
363
477
  --repo Force single-repo template
364
- --force Overwrite existing files
478
+ --force Overwrite existing files (install, upgrade). patch never overwrites
479
+ --dir <path> factory: jobs directory (default: ./factory/jobs)
480
+ --interval <ms>
481
+ factory watch: refresh interval (default: 2000, minimum: 100)
482
+ --all factory status/watch: include closed and archived jobs
483
+ factory archive: include every closed job, not just those older than 7 days
484
+ factory prune: include every merged job, not just those older than 7 days
485
+ --abandoned factory prune: also delete abandoned jobs (default: merged only)
486
+ --dry-run factory archive/prune: list only, change nothing
487
+ --yes factory prune: actually delete (without it, prune only lists)
365
488
  -h, --help Show this help
366
489
  `);
367
490
  }
368
491
 
369
492
  function main() {
370
- const flags = parseFlags(process.argv.slice(2));
371
- if (flags.help) {
372
- usage();
373
- return;
374
- }
375
- const cmd = flags._[0];
376
493
  const cwd = process.cwd();
377
494
  try {
495
+ const flags = parseFlags(process.argv.slice(2));
496
+ if (flags.help) {
497
+ usage();
498
+ return;
499
+ }
500
+ const cmd = flags._[0];
378
501
  switch (cmd) {
379
502
  case 'install': return cmdInstall(cwd, flags);
380
503
  case 'patch': return cmdPatch(cwd, flags);
381
504
  case 'upgrade': return cmdUpgrade(cwd, flags);
382
505
  case 'doctor': return cmdDoctor(cwd, flags);
383
506
  case 'omo-slim-starter': return cmdOmoStarter(cwd, flags);
507
+ case 'factory': return cmdFactory(cwd, flags);
384
508
  default:
385
509
  if (cmd) err(`Unknown command: ${cmd}`);
386
510
  usage();
@@ -397,6 +521,7 @@ module.exports = {
397
521
  detectType,
398
522
  scopeFiles,
399
523
  templateFiles,
524
+ installName,
400
525
  copyMissing,
401
526
  scanPlaceholders,
402
527
  envHeader,
@@ -406,7 +531,9 @@ module.exports = {
406
531
  cmdPatch,
407
532
  cmdDoctor,
408
533
  cmdUpgrade,
534
+ upgradeArgs,
409
535
  cmdOmoStarter,
536
+ cmdFactory,
410
537
  ENV_DIRS,
411
538
  SHARED_ROOT,
412
539
  SHARED_DIRS,
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ // `factory archive`: moves closed jobs (merged, abandoned) from factory/jobs/<id>/
4
+ // to factory/archive/<id>/. Default only archives jobs closed more than 7 days ago;
5
+ // --all archives every closed job. Open jobs are never touched. Stdlib only.
6
+ // Selection is shared with `factory prune` (factory.selectClosedJobs).
7
+ // Never touches git or branches: those are left to /factory archive and /factory prune.
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const factory = require('./factory-watch.js');
12
+
13
+ function cmdFactoryArchive(cwd, flags = {}, opts = {}) {
14
+ const stdout = opts.stdout || process.stdout;
15
+ const now = opts.now === undefined ? Date.now() : opts.now;
16
+ const all = Boolean(flags.all);
17
+ const dryRun = Boolean(flags.dryRun);
18
+ const jobsDir = factory.resolveDir(cwd, flags);
19
+ const archiveDir = factory.archiveDirOf(jobsDir);
20
+ const line = (text) => stdout.write(text + '\n');
21
+ const result = {
22
+ dir: jobsDir, archiveDir, dryRun, exists: true,
23
+ moved: [], skippedOpen: 0, kept: 0, skippedInvalid: [], skippedArchived: [],
24
+ };
25
+
26
+ const sel = factory.selectClosedJobs(jobsDir, { all, now });
27
+ if (!sel.exists) {
28
+ result.exists = false;
29
+ line(`• No jobs directory at ${jobsDir} — nothing to archive.`);
30
+ return result;
31
+ }
32
+ result.skippedOpen = sel.skippedOpen;
33
+ result.kept = sel.kept;
34
+ result.skippedInvalid = sel.skippedInvalid;
35
+
36
+ for (const c of sel.candidates) {
37
+ const target = path.join(archiveDir, c.id);
38
+ if (fs.existsSync(target)) {
39
+ result.skippedArchived.push(c.id);
40
+ continue;
41
+ }
42
+ if (!dryRun) {
43
+ fs.mkdirSync(archiveDir, { recursive: true });
44
+ fs.renameSync(c.dir, target);
45
+ }
46
+ result.moved.push({ id: c.id, stage: c.stage });
47
+ }
48
+
49
+ const verb = dryRun ? 'Would move' : 'Moved';
50
+ if (result.moved.length) {
51
+ line(`${verb} ${result.moved.length} closed job(s) to ${archiveDir}:`);
52
+ for (const { id, stage } of result.moved) line(` ${id} ${stage}`);
53
+ } else {
54
+ line('No closed jobs to move.');
55
+ }
56
+ line(`Skipped ${result.skippedOpen} open job(s).`);
57
+ if (result.skippedInvalid.length) {
58
+ line(`Skipped ${result.skippedInvalid.length} invalid (not moved):`);
59
+ for (const { id, reason } of result.skippedInvalid) line(` ${id} ${reason}`);
60
+ }
61
+ if (result.skippedArchived.length) {
62
+ line(`Skipped ${result.skippedArchived.length} already in archive (not overwritten):`);
63
+ for (const id of result.skippedArchived) line(` ${id}`);
64
+ }
65
+ if (!all && result.kept) {
66
+ line(`Kept ${result.kept} closed job(s) newer than ${factory.DEFAULT_AGE_DAYS} days (--all moves them too).`);
67
+ }
68
+ return result;
69
+ }
70
+
71
+ module.exports = { cmdFactoryArchive };