@techierathore/techieflow 1.0.1 → 1.0.3

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 (178) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +0 -5
  2. package/.claude/commands/TechieFlow/agents/architect.md +0 -6
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +41 -215
  4. package/.claude/commands/TechieFlow/agents/verifier.md +23 -64
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
  6. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
  7. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
  8. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
  9. package/.claude/commands/TechieFlow/tasks/amend-docs.md +20 -143
  10. package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
  11. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
  12. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
  13. package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
  14. package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
  15. package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
  16. package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
  17. package/.claude/commands/TechieFlow/tasks/metrics-report.md +1 -1
  18. package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
  19. package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
  20. package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
  21. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
  22. package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
  23. package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
  24. package/.opencode/plugin/techieflow.js +39 -12
  25. package/.tfcore/agents/analyst.md +0 -5
  26. package/.tfcore/agents/architect.md +0 -6
  27. package/.tfcore/agents/flow-master.md +41 -215
  28. package/.tfcore/agents/verifier.md +23 -64
  29. package/.tfcore/core-config.yaml +10 -0
  30. package/.tfcore/hooks/block-git.sh +4 -4
  31. package/.tfcore/hooks/guard-artifacts.sh +2 -2
  32. package/.tfcore/hooks/guard-build.sh +76 -0
  33. package/.tfcore/hooks/guard-db.sh +90 -0
  34. package/.tfcore/hooks/guard-metrics.sh +56 -0
  35. package/.tfcore/hooks/guard-status-html.sh +116 -71
  36. package/.tfcore/hooks/guard-status.sh +43 -16
  37. package/.tfcore/hooks/guard-verify.sh +70 -48
  38. package/.tfcore/hooks/sweep-artifacts.sh +2 -2
  39. package/.tfcore/install-manifest.yaml +0 -18
  40. package/.tfcore/routing.yaml +25 -14
  41. package/.tfcore/standards/coding-standards-core.md +61 -0
  42. package/.tfcore/standards/coding-standards-dotnet.md +93 -0
  43. package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
  44. package/.tfcore/tasks/_smoke-test-policy.md +20 -84
  45. package/.tfcore/tasks/_status-update-gate.md +10 -74
  46. package/.tfcore/tasks/_yolo-mode.md +20 -58
  47. package/.tfcore/tasks/amend-docs.md +20 -143
  48. package/.tfcore/tasks/build-phase.md +14 -224
  49. package/.tfcore/tasks/day1-brownfield.md +31 -505
  50. package/.tfcore/tasks/day1-greenfield.md +28 -213
  51. package/.tfcore/tasks/deploy-checklist.md +19 -0
  52. package/.tfcore/tasks/devguide.md +10 -219
  53. package/.tfcore/tasks/fix-issues.md +10 -129
  54. package/.tfcore/tasks/log-miss.md +19 -158
  55. package/.tfcore/tasks/metrics-report.md +1 -1
  56. package/.tfcore/tasks/mockups.md +12 -117
  57. package/.tfcore/tasks/refresh-status.md +9 -130
  58. package/.tfcore/tasks/split-brd.md +10 -126
  59. package/.tfcore/tasks/triage-and-fix.md +14 -0
  60. package/.tfcore/tasks/triage-issues.md +11 -152
  61. package/.tfcore/tasks/verify-phase.md +36 -572
  62. package/.tfcore/telemetry/SCHEMA.md +43 -7
  63. package/.tfcore/telemetry/tf-metrics.sh +152 -17
  64. package/.tfcore/templates/project-brief-tmpl.yaml +1 -1
  65. package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
  66. package/.tfcore/templates/stack-questions.md +77 -0
  67. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
  68. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
  69. package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
  70. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
  71. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
  72. package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
  73. package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
  74. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
  75. package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
  76. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
  77. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
  78. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
  79. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
  80. package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
  81. package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
  82. package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
  83. package/.tfcore/user-guide.md +0 -23
  84. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  85. package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
  86. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  87. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  88. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  89. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  90. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  91. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  92. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  93. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  94. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  95. package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
  96. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  97. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  98. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  99. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  100. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  101. package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
  102. package/.tfcore/utils/tf-brd-status.py +176 -0
  103. package/.tfcore/utils/tf-brd-status.sh +19 -0
  104. package/.tfcore/utils/tf-build-list.py +191 -0
  105. package/.tfcore/utils/tf-build-list.sh +20 -0
  106. package/.tfcore/utils/tf-build.sh +143 -0
  107. package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
  108. package/.tfcore/utils/tf-checklist-edit.py +133 -0
  109. package/.tfcore/utils/tf-day1-files.py +208 -0
  110. package/.tfcore/utils/tf-day1-files.sh +20 -0
  111. package/.tfcore/utils/tf-devguide-list.py +249 -0
  112. package/.tfcore/utils/tf-devguide-list.sh +18 -0
  113. package/.tfcore/utils/tf-doc-check.py +1267 -0
  114. package/.tfcore/utils/tf-doc-check.sh +29 -0
  115. package/.tfcore/utils/tf-emit.sh +203 -13
  116. package/.tfcore/utils/tf-fix-close.sh +83 -0
  117. package/.tfcore/utils/tf-goal.sh +193 -62
  118. package/.tfcore/utils/tf-harness.sh +4 -9
  119. package/.tfcore/utils/tf-log-miss.py +197 -0
  120. package/.tfcore/utils/tf-log-miss.sh +14 -0
  121. package/.tfcore/utils/tf-misses-md.py +211 -0
  122. package/.tfcore/utils/tf-misses-md.sh +13 -0
  123. package/.tfcore/utils/tf-mockups-locate.py +66 -0
  124. package/.tfcore/utils/tf-mockups-locate.sh +18 -0
  125. package/.tfcore/utils/tf-opencode-audit.py +23 -7
  126. package/.tfcore/utils/tf-perf-grade.py +97 -0
  127. package/.tfcore/utils/tf-perf-grade.sh +12 -0
  128. package/.tfcore/utils/tf-phase.sh +74 -0
  129. package/.tfcore/utils/tf-routing-bind.sh +10 -5
  130. package/.tfcore/utils/tf-routing.sh +6 -7
  131. package/.tfcore/utils/tf-split-brd.py +304 -0
  132. package/.tfcore/utils/tf-split-brd.sh +22 -0
  133. package/.tfcore/utils/tf-status-evidence.sh +68 -0
  134. package/.tfcore/utils/tf-status-facts.py +309 -0
  135. package/.tfcore/utils/tf-status-facts.sh +20 -0
  136. package/.tfcore/utils/tf-triage.py +243 -0
  137. package/.tfcore/utils/tf-triage.sh +16 -0
  138. package/.tfcore/utils/tf-verify-boot.sh +250 -0
  139. package/.tfcore/utils/tf-verify-emit.sh +110 -0
  140. package/.tfcore/utils/tf-verify-env.sh +88 -0
  141. package/.tfcore/utils/tf-verify-list.py +270 -0
  142. package/.tfcore/utils/tf-verify-list.sh +13 -0
  143. package/.tfcore/utils/tf-verify-screens.mjs +283 -0
  144. package/.tfcore/utils/tf-verify-screens.sh +22 -0
  145. package/.tfcore/utils/tf-verify-tests.sh +123 -0
  146. package/.tfcore/utils/tf-verify-verdict.py +283 -0
  147. package/.tfcore/utils/tf-verify-verdict.sh +15 -0
  148. package/.tfcore/utils/tf-yolo.sh +59 -0
  149. package/README.md +114 -1149
  150. package/docs/TechieFlow-Installation.md +27 -7
  151. package/opencode.jsonc +13 -33
  152. package/package.json +4 -7
  153. package/scripts/install.mjs +73 -46
  154. package/scripts/npm-cleanup.mjs +137 -0
  155. package/scripts/npm-postinstall.mjs +49 -0
  156. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
  157. package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
  158. package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
  159. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
  160. package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
  161. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
  162. package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
  163. package/.codex/config.toml +0 -46
  164. package/.codex/hooks.json +0 -63
  165. package/.codex/rules/techieflow.rules +0 -8
  166. package/.tfcore/hooks/codex-adapter.py +0 -248
  167. package/.tfcore/tasks/advanced-elicitation.md +0 -117
  168. package/.tfcore/tasks/author-brd.md +0 -79
  169. package/.tfcore/tasks/document-project.md +0 -343
  170. package/.tfcore/tasks/execute-checklist.md +0 -86
  171. package/.tfcore/tasks/index-docs.md +0 -173
  172. package/.tfcore/tasks/kb-mode-interaction.md +0 -75
  173. package/.tfcore/tasks/shard-doc.md +0 -185
  174. package/.tfcore/utils/tf-codex-bind.py +0 -196
  175. package/.tfcore/utils/tf-codex-telemetry.py +0 -60
  176. package/WORKFLOW.html +0 -1986
  177. package/scripts/test-install.mjs +0 -505
  178. package/scripts/validate.mjs +0 -163
@@ -17,7 +17,7 @@ You need four things on the machine.
17
17
  |---|---|---|
18
18
  | Node.js 20 or newer | Runs the installer. Nothing is added to your project. | `node --version` |
19
19
  | bash | The framework's guard hooks and helper scripts run under bash. macOS and Linux have it. On Windows, work inside WSL or Git Bash. | `bash --version` |
20
- | Python 3.10 or newer | Powers the HTML renderer, the telemetry writer and the guard hooks. An older Python 3 works but skips the Codex files. | `python3 --version` |
20
+ | Python 3 | Powers the HTML renderer, the telemetry writer and the guard hooks. | `python3 --version` |
21
21
  | Claude Code or OpenCode | The harness that runs the agents. Either one. Both work from the same install. | `claude --version` or `opencode --version` |
22
22
 
23
23
  For a .NET project you also need the .NET SDK. Run `dotnet --version` to check.
@@ -30,6 +30,8 @@ You do not need rsync, git on the command line, or a clone of this repository.
30
30
 
31
31
  The installer copies the framework into hidden folders inside your project. It never adds the framework as a dependency of your application. After it runs there is no `node_modules`, no `package.json` and no lock file that was not there before.
32
32
 
33
+ The npm package itself holds the framework folders, this document and a `scripts/` folder with the installer's own three files. Nothing from `scripts/` is ever copied into your project, so it cannot clash with a `scripts/` folder of your own. Claude Code and OpenCode never read the package. They read the copies the installer places in your project, listed below.
34
+
33
35
  | Path | What it is | On update |
34
36
  |---|---|---|
35
37
  | `.tfcore/` | The framework: personas, tasks, templates, hooks, helper scripts. | Refreshed. Two files inside are yours and are kept: `core-config.yaml` and `routing.yaml`. |
@@ -40,8 +42,6 @@ The installer copies the framework into hidden folders inside your project. It n
40
42
  | `.opencode/opencode.jsonc` | The framework's OpenCode configuration. OpenCode reads the personas and tasks straight from `.tfcore/` through it. | Refreshed. |
41
43
  | `.opencode/command/generate-html.md` | The short `/generate-html` command for OpenCode. | Added on update only. |
42
44
  | `opencode.jsonc` | The root OpenCode configuration, for your own additions. | Refreshed only when it holds nothing of yours, with the old file kept as `opencode.jsonc.bak`. |
43
- | `.codex/` and `.agents/skills/` | The Codex adapter, generated from the same personas and tasks. | Refreshed. `config.toml` is yours and is kept. |
44
- | `WORKFLOW.html` | The human workflow guide. Open it in a browser. | Refreshed. |
45
45
  | `.tf-scaffold-note.txt` | A note with your next command. Delete it when read. | Left alone. |
46
46
  | `docs/metrics/` | Five empty telemetry files and a README. This is your project's history: commit it. | Left alone. |
47
47
  | `.gitignore` | Lines that keep the copies above out of your commits. Appended, never rewritten. | Appended. |
@@ -240,7 +240,6 @@ The installer and the updater never write to:
240
240
  - `.tfcore/core-config.yaml` and `.tfcore/routing.yaml` after the first install
241
241
  - `.claude/settings.local.json`
242
242
  - `.claude/commands/trblazeui.md`, `.claude/commands/techierag.md` and the same names under `.opencode/command/`. These come from the NuGet packages.
243
- - `.codex/config.toml` after the first install
244
243
  - `opencode.jsonc` at the root when it holds a key of your own
245
244
 
246
245
  Existing lines in `.gitignore` and `.gitattributes` are never rewritten. The managed lines are appended once and recognised on every later run.
@@ -282,17 +281,38 @@ git clone https://github.com/techierathore/TechieFlow.git
282
281
 
283
282
  The installer and the scripts produce the same files. A test in the repository, `scripts/test-install.mjs`, runs both on the same folders and fails on any difference. Choose the route you like. The package route needs no clone and always gives you the released version. The clone route gives you whatever is on the branch you checked out.
284
283
 
285
- Do not run `npm install @techierathore/techieflow`. That would add the package to your application. Use `npx`, which runs the installer once and keeps nothing.
284
+ ---
285
+
286
+ ## 9. If you ran `npm install` instead of `npx`
287
+
288
+ The documented command is `npx ... install`. It downloads the package to npm's cache, runs the installer once, and leaves nothing behind.
289
+
290
+ `npm install @techierathore/techieflow` is the wrong command for a framework. It treats the package as a dependency of your application: it puts the whole package under `node_modules/@techierathore/techieflow/`, and writes it into `package.json` and `package-lock.json`. Claude Code and OpenCode do not look there.
291
+
292
+ What happens next depends on your npm version. Check with `npm --version`.
293
+
294
+ | npm version | What happens |
295
+ |---|---|
296
+ | 10 or 11 | The package's install hook runs the installer for you, then removes the package from `node_modules/`, `package.json` and `package-lock.json`. If npm created those files only for this install, they are removed too. A JavaScript project keeps its own `package.json`, lock file and `node_modules/`, minus the framework entry. The end result is the same as the `npx` command. npm hides the installer's output, so read `.tf-scaffold-note.txt` for the next step. |
297
+ | 12 or newer | npm no longer runs install hooks unless the project allows them. Nothing is installed. The package just sits under `node_modules/`. |
298
+
299
+ Either way, the fix is the same. Run the real command in the project folder:
300
+
301
+ ```bash
302
+ npx @techierathore/techieflow@latest install
303
+ ```
304
+
305
+ It installs the framework if it is not there yet, then removes the leftover package from `node_modules/`, `package.json` and `package-lock.json`. `update` does the same tidy-up.
286
306
 
287
307
  ---
288
308
 
289
- ## 9. If something goes wrong
309
+ ## 10. If something goes wrong
290
310
 
291
311
  | What you see | What to do |
292
312
  |---|---|
313
+ | The framework is under `node_modules/@techierathore/techieflow/` and nowhere else | You ran `npm install`. See section 9. Run `npx @techierathore/techieflow@latest install`. |
293
314
  | `bash was not found` | On Windows, run the command inside WSL or Git Bash. |
294
315
  | `python3 was not found` | Install Python 3 and run the command again. On macOS: `brew install python3`. On Ubuntu or WSL: `sudo apt-get install -y python3`. |
295
- | `Codex bindings could not be generated` | Your Python is older than 3.10. Everything except the Codex files is installed. Upgrade Python and run `update` to add them. |
296
316
  | `Refusing to install into the framework itself` | You ran the command inside a clone of this repository. Pass `--target=<your project>`. |
297
317
  | `does not look installed` on update | The folder has no `.tfcore/`. Run `install` first. |
298
318
  | Claude Code does not show the `/TechieFlow:agents:analyst` command | Restart Claude Code in the project folder. Check that `.claude/commands/TechieFlow/agents/analyst.md` exists. |
package/opencode.jsonc CHANGED
@@ -253,7 +253,7 @@
253
253
  "sudo *": "ask"
254
254
  }
255
255
  },
256
- "description": "Madhav — the single super-agent. Orchestrates the full pipeline (*run-workflow, *phase) with parallel subagents, runs the unified build (*build-phase — calls /trblazeui + /techierag as sub-agents), fixes bugs from screenshots (*fix-issues), analyzes + logs human-found bugs without fixing (*triage-issues), records a single missed requirement in seconds (*log-miss), reports development telemetry (*metrics), and handles doc/status utilities (*render-workflow-docs, *generate-html, *handoff-phase, *refresh-status, *devguide) plus any one-off task."
256
+ "description": "Madhav — the TechieFlow master: build (*build-phase), fix (*fix-issues), triage (*triage-issues), log a miss (*log-miss), amend documents (*amend-docs), DevGuide and ProductGuide, handoff, refresh-status, metrics, HTML rendering, YOLO. The analyst owns day-1; the verifier owns *verify.",
257
257
  },
258
258
  "flow-architect": {
259
259
  "prompt": "{file:./.tfcore/agents/architect.md}",
@@ -839,19 +839,23 @@
839
839
  },
840
840
  "techieflow:tasks:fix-issues": {
841
841
  "template": "{file:./.tfcore/tasks/fix-issues.md}",
842
- "description": "Bug-fix front door: given a folder of screenshots (+ optional description), reproduce with Playwright, triage (layout/data/logic/RAG), fan fixes to the right builder, re-smoke (data+visual) + re-verify, update docs. Driven by /flow-master."
842
+ "description": "Fixes the bugs in a folder of screenshots: triages and logs them, builds the fix through the builders in FIX mode, smokes, re-verifies the touched rows, closes the misses with the fix cost (tf-fix-close.sh). Driven by /flow-master."
843
843
  },
844
844
  "techieflow:tasks:log-miss": {
845
845
  "template": "{file:./.tfcore/tasks/log-miss.md}",
846
- "description": "The 20-second front door for 'you missed this': turns one sentence about something an agent got wrong into a misses.jsonl record (what was missed, which phase/agent/model let it through, who found it) plus the checklist line — demotion with a dated miss Remark, or a new Planned row when nothing owns it. Never boots the app, never reproduces, never edits code. Driven by /flow-master."
846
+ "description": "The twenty-second record: one sentence from the owner becomes one miss record and one checklist line through tf-log-miss.sh. Never boots, never fixes. Driven by /flow-master."
847
847
  },
848
848
  "techieflow:tasks:triage-issues": {
849
849
  "template": "{file:./.tfcore/tasks/triage-issues.md}",
850
- "description": "ANALYZE-ONLY bug front door for human-found (UAT/production) bugs: reproduce with Playwright, triage to the owning REQ, log in the checklist (demote to Needs re-verify / new Planned rows), optional scoped regression re-verify, update PROJECT-STATUS with the *fix-issues pointer. NEVER edits code. Driven by /flow-master."
850
+ "description": "Analyse-only front door for bugs a person found: reproduces each on its screen, logs it in the checklist and the telemetry through tf-triage.sh (demote, new, note, close), never edits code. Driven by /flow-master."
851
+ },
852
+ "techieflow:tasks:triage-and-fix": {
853
+ "template": "{file:./.tfcore/tasks/triage-and-fix.md}",
854
+ "description": "The owner's whole bug sequence in YOLO: compare every screen to its mockup, triage the reported bugs, log every root cause with its cost, fix, log every fix with its cost, refresh the metrics, one summary per step. Driven by /flow-master."
851
855
  },
852
856
  "techieflow:tasks:verify-phase": {
853
857
  "template": "{file:./.tfcore/tasks/verify-phase.md}",
854
- "description": "Autonomous verification of a scope (ui | functional | all | REQ list | legacy phase-N) — filters the one Checklist by REQ prefix; applies the data-render + visual-truth gates; verdicts land in the Checklist Requirements Status table."
858
+ "description": "Verifies every checklist row in a scope (ui | functional | all | REQ list) against the running app: boots it, runs the seven checks in order through the tf-verify-* scripts, writes the verdicts into the checklist and the telemetry. YOLO by default. Driven by /verifier."
855
859
  },
856
860
  "techieflow:tasks:handoff-phase": {
857
861
  "template": "{file:./.tfcore/tasks/handoff-phase.md}",
@@ -873,14 +877,14 @@
873
877
  "template": "{file:./.tfcore/tasks/generate-html.md}",
874
878
  "description": "Render any markdown file(s) or a non-recursive directory to self-contained HTML using the shared shell."
875
879
  },
876
- "techieflow:tasks:author-brd": {
877
- "template": "{file:./.tfcore/tasks/author-brd.md}",
878
- "description": "Interactively extend docs/{AppName}-BRD.md with confirmed BRD-N requirements (per-item elicitation)."
879
- },
880
880
  "techieflow:tasks:amend-docs": {
881
881
  "template": "{file:./.tfcore/tasks/amend-docs.md}",
882
882
  "description": "Fold an evolving concept / changed requirements into the EXISTING day-1 docs IN PLACE: surgically amends BRD + Architecture (append-only IDs), ripples to PROJECT-STATUS / BRD §4 / checklists, re-renders HTML. Incremental alternative to re-running day1-*."
883
883
  },
884
+ "techieflow:tasks:deploy-checklist": {
885
+ "template": "{file:./.tfcore/tasks/deploy-checklist.md}",
886
+ "description": "Deployment Checklist: the steps to put the application on its host, one document per hosting target, written after UAT from the owner's pipeline guidance document and the Stack answers Q9 and Q10 (docs/TechieFlow-Document-Schemas.md §3.10). Never deploys. Driven by /flow-master."
887
+ },
884
888
  "techieflow:tasks:productguide": {
885
889
  "template": "{file:./.tfcore/tasks/productguide.md}",
886
890
  "description": "End-user Product Guide: screenshot-illustrated, task-oriented how-to manual for external users (what each screen is for + how to do things) — the user-facing sibling of the DevGuide, reusing its captured screenshots. Always MD + HTML. On-demand. Driven by /flow-master."
@@ -889,10 +893,6 @@
889
893
  "template": "{file:./.tfcore/tasks/devguide.md}",
890
894
  "description": "Generate/refresh the screen-by-screen Developer Guide: traces every screen/control from Razor page → service → data-access → stored proc/query, per user role, documenting the code AS BUILT. Single doc for small apps, split per role for large ones; --update refreshes only changed screens."
891
895
  },
892
- "techieflow:tasks:document-project": {
893
- "template": "{file:./.tfcore/tasks/document-project.md}",
894
- "description": "Generate comprehensive documentation for existing projects optimized for AI development agents."
895
- },
896
896
  "techieflow:tasks:create-doc": {
897
897
  "template": "{file:./.tfcore/tasks/create-doc.md}",
898
898
  "description": "Create a document from a YAML-driven TechieFlow template (interactive, section by section)."
@@ -904,26 +904,6 @@
904
904
  "techieflow:tasks:facilitate-brainstorming-session": {
905
905
  "template": "{file:./.tfcore/tasks/facilitate-brainstorming-session.md}",
906
906
  "description": "Run a structured brainstorming session; results land in docs/brainstorming-session-results.md."
907
- },
908
- "techieflow:tasks:advanced-elicitation": {
909
- "template": "{file:./.tfcore/tasks/advanced-elicitation.md}",
910
- "description": "Reflective/brainstorming actions for deeper exploration and iterative refinement of drafted content."
911
- },
912
- "techieflow:tasks:execute-checklist": {
913
- "template": "{file:./.tfcore/tasks/execute-checklist.md}",
914
- "description": "Validate a document or work product against one of the TechieFlow checklists."
915
- },
916
- "techieflow:tasks:index-docs": {
917
- "template": "{file:./.tfcore/tasks/index-docs.md}",
918
- "description": "Maintain docs/index.md by scanning all documentation files and indexing them with descriptions."
919
- },
920
- "techieflow:tasks:kb-mode-interaction": {
921
- "template": "{file:./.tfcore/tasks/kb-mode-interaction.md}",
922
- "description": "User-friendly interface to the TechieFlow knowledge base."
923
- },
924
- "techieflow:tasks:shard-doc": {
925
- "template": "{file:./.tfcore/tasks/shard-doc.md}",
926
- "description": "Split a large document into smaller documents by level-2 sections, preserving content integrity."
927
907
  }
928
908
  }
929
909
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techierathore/techieflow",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "TechieFlow: a spec-driven development framework for AI coding agents, for Claude Code and OpenCode. One command installs it into any project.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -33,19 +33,16 @@
33
33
  ".opencode/command",
34
34
  "!.opencode/command/trblazeui.md",
35
35
  "!.opencode/command/techierag.md",
36
- ".codex/config.toml",
37
- ".codex/hooks.json",
38
- ".codex/rules",
39
36
  "opencode.jsonc",
40
- "WORKFLOW.html",
41
37
  "scripts/install.mjs",
42
- "scripts/test-install.mjs",
43
- "scripts/validate.mjs",
38
+ "scripts/npm-postinstall.mjs",
39
+ "scripts/npm-cleanup.mjs",
44
40
  "docs/TechieFlow-Installation.md",
45
41
  "LICENSE",
46
42
  "README.md"
47
43
  ],
48
44
  "scripts": {
45
+ "postinstall": "node scripts/npm-postinstall.mjs",
49
46
  "validate": "node scripts/validate.mjs",
50
47
  "test:install": "node scripts/test-install.mjs",
51
48
  "pack:check": "npm pack --dry-run",
@@ -20,7 +20,7 @@
20
20
  // scripts/test-install.mjs proves that by running both routes and comparing the results.
21
21
  //
22
22
  // Needs: node, bash and python3. The framework's own helper scripts (telemetry setup,
23
- // build-output ignore audit, Codex bindings) are run exactly as the shell scripts run them.
23
+ // build-output ignore audit) are run exactly as the shell scripts run them.
24
24
 
25
25
  import { spawnSync } from "node:child_process";
26
26
  import {
@@ -31,6 +31,7 @@ import { homedir } from "node:os";
31
31
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
32
32
  import { createInterface } from "node:readline/promises";
33
33
  import { fileURLToPath } from "node:url";
34
+ import { hasDependencyFootprint, removeDependencyFootprint } from "./npm-cleanup.mjs";
34
35
 
35
36
  // ---------------------------------------------------------------- arguments
36
37
 
@@ -200,7 +201,7 @@ function checkTools() {
200
201
  }
201
202
  pythonCommand = findPython();
202
203
  if (!pythonCommand) {
203
- throw new Error("python3 was not found. It powers the HTML renderer, the telemetry writer, the Codex bindings and every guard hook.\n"
204
+ throw new Error("python3 was not found. It powers the HTML renderer, the telemetry writer and every guard hook.\n"
204
205
  + " Install it and run this command again:\n"
205
206
  + " macOS: brew install python3\n"
206
207
  + " Ubuntu / WSL: sudo apt-get install -y python3\n"
@@ -209,6 +210,22 @@ function checkTools() {
209
210
  }
210
211
  }
211
212
 
213
+ // Someone ran `npm install @techierathore/techieflow` instead of the npx command. On npm 10
214
+ // and 11 the package's install hook deploys the framework and removes its own npm files.
215
+ // npm 12 and newer skip install hooks, so the package just sits under node_modules/ and in
216
+ // package.json. Whichever way it happened, once the framework is in place those npm files
217
+ // have no purpose: remove them. Skipped while the install hook itself is running (the
218
+ // package folder is still in use by npm then; the hook's own cleanup handles that case).
219
+ function tidyDependencyInstall() {
220
+ const packageDir = join(target, "node_modules", "@techierathore", "techieflow");
221
+ if (sourceRoot === packageDir || sourceRoot.startsWith(`${packageDir}${sep}`)) return;
222
+ if (!hasDependencyFootprint(target)) return;
223
+ say("");
224
+ say(" The package was found under node_modules/ (someone ran `npm install` on it). The framework is not an application dependency.");
225
+ if (dryRun) { say(" WOULD remove it from node_modules/, package.json and package-lock.json."); return; }
226
+ for (const item of removeDependencyFootprint(target)) say(` removed ${item}`);
227
+ }
228
+
212
229
  function ensureSafeTarget() {
213
230
  if (target === sourceRoot || target.startsWith(`${sourceRoot}${sep}`)) throw new Error("Refusing to install into the framework itself. Pass --target=<your project folder>.");
214
231
  if (target === resolve("/") || target === resolve(homedir())) throw new Error("Refusing to install into the filesystem root or your home folder.");
@@ -220,8 +237,8 @@ function ensureSafeTarget() {
220
237
  // Same lines, same patterns and same header text as the shell scripts. Append-only: an
221
238
  // entry already present in any anchored or slash variant is respected, nothing is rewritten.
222
239
  const frameworkIgnore = {
223
- lines: [".tfcore/", ".claude/", ".opencode/", ".codex/", ".agents/skills/", "/CLAUDE.md", "/WORKFLOW.html", "/opencode.jsonc", "/.tf-scaffold-note.txt"],
224
- patterns: [/^\/?\.tfcore\/?$/, /^\/?\.claude\/?$/, /^\/?\.opencode\/?$/, /^\/?\.codex\/?$/, /^\/?\.agents\/skills\/?$/, /^\/?CLAUDE\.md$/, /^\/?WORKFLOW\.html$/, /^\/?opencode\.jsonc$/, /^\/?\.tf-scaffold-note\.txt$/],
240
+ lines: [".tfcore/", ".claude/", ".opencode/", "/CLAUDE.md", "/opencode.jsonc", "/.tf-scaffold-note.txt"],
241
+ patterns: [/^\/?\.tfcore\/?$/, /^\/?\.claude\/?$/, /^\/?\.opencode\/?$/, /^\/?CLAUDE\.md$/, /^\/?opencode\.jsonc$/, /^\/?\.tf-scaffold-note\.txt$/],
225
242
  header: ["# TechieFlow framework — deployed copies, never commit (managed by scaffold/update-framework.sh)"],
226
243
  label: "framework entries",
227
244
  };
@@ -336,6 +353,22 @@ const canonicalSettings = String.raw`{
336
353
  {
337
354
  "type": "command",
338
355
  "command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-artifacts.sh\""
356
+ },
357
+ {
358
+ "type": "command",
359
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-status.sh\""
360
+ },
361
+ {
362
+ "type": "command",
363
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-metrics.sh\""
364
+ },
365
+ {
366
+ "type": "command",
367
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-db.sh\""
368
+ },
369
+ {
370
+ "type": "command",
371
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-build.sh\""
339
372
  }
340
373
  ]
341
374
  },
@@ -346,6 +379,10 @@ const canonicalSettings = String.raw`{
346
379
  "type": "command",
347
380
  "command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-status.sh\""
348
381
  },
382
+ {
383
+ "type": "command",
384
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-metrics.sh\""
385
+ },
349
386
  {
350
387
  "type": "command",
351
388
  "command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-verify.sh\""
@@ -409,7 +446,6 @@ Added (only missing files filled — re-runs are safe):
409
446
  .tfcore/ ← TechieFlow v4 customized (agents, tasks, templates)
410
447
  .claude/commands/ ← Claude Code slash commands (TechieFlow agents)
411
448
  .claude/settings.json ← yolo-except-git-writes permissions
412
- WORKFLOW.html ← the human workflow guide (open in a browser; §17 = macOS / Windows / Linux)
413
449
  opencode.jsonc ← OpenCode config (loads agents/tasks from .tfcore/ via {file:...} refs)
414
450
  .gitignore ← framework entries appended (deployed copies stay uncommitted)
415
451
 
@@ -451,7 +487,6 @@ Folders/files created (only missing files filled — re-runs are safe):
451
487
  .tfcore/ ← TechieFlow v4 customized (agents, tasks, templates)
452
488
  .claude/commands/ ← Claude Code slash commands (TechieFlow agents)
453
489
  .claude/settings.json ← yolo-except-git-writes permissions
454
- WORKFLOW.html ← the human workflow guide (open in a browser; §17 = macOS / Windows / Linux)
455
490
  opencode.jsonc ← OpenCode config (loads agents/tasks from .tfcore/ via {file:...} refs)
456
491
  .gitignore ← framework entries appended (deployed copies stay uncommitted)
457
492
  tests/playwright/ tests/unit/ src/
@@ -498,15 +533,20 @@ function deployOpenCodeBridge() {
498
533
  writeText(join(target, ".opencode", "opencode.jsonc"), openCodeConfigCopy());
499
534
  }
500
535
 
501
- function deployCodexAdapter() {
502
- say(" .codex/ + .agents/skills/ — Codex adapter");
503
- if (dryRun) { say(" WOULD preserve/create .codex/config.toml; refresh hooks/rules; regenerate agents/skills"); return; }
504
- for (const folder of [".codex/agents", ".codex/rules", ".agents/skills"]) mkdirSync(join(target, folder), { recursive: true });
505
- copyFile(join(sourceRoot, ".codex", "config.toml"), join(target, ".codex", "config.toml"), { onlyIfMissing: true });
506
- copyFile(join(sourceRoot, ".codex", "hooks.json"), join(target, ".codex", "hooks.json"));
507
- copyFile(join(sourceRoot, ".codex", "rules", "techieflow.rules"), join(target, ".codex", "rules", "techieflow.rules"));
508
- const result = runPython([join(target, ".tfcore", "utils", "tf-codex-bind.py"), target]);
509
- if (result.status !== 0) say(" ⚠ Codex bindings could not be generated (python3 required)");
536
+ // The Codex adapter was removed on 2026-09-07 (D-14, FR-42) and WORKFLOW.html was dropped the
537
+ // same day: it was a second full description of the process, last revised before the reset, and
538
+ // it still taught commands that no longer exist. Both are taken out of a project that has them.
539
+ // Nothing here is project content: .codex/ held a config file plus generated bindings,
540
+ // .agents/skills/ was generated in full from .tfcore/tasks/, and WORKFLOW.html was a copy.
541
+ function removeRetiredFiles() {
542
+ for (const path of [".codex", ".agents/skills", "WORKFLOW.html"]) {
543
+ const full = join(target, path);
544
+ if (!existsSync(full)) continue;
545
+ if (dryRun) { say(` ${would}remove ${path} — no longer part of the framework`); continue; }
546
+ rmSync(full, { recursive: true, force: true });
547
+ say(` removed ${path} — no longer part of the framework`);
548
+ }
549
+ if (!dryRun) { try { rmdirSync(join(target, ".agents")); say(" removed the emptied .agents/"); } catch {} }
510
550
  }
511
551
 
512
552
  function deployHousekeeping() {
@@ -602,13 +642,11 @@ async function install() {
602
642
  say(" syncing agent files from .tfcore/agents/ → .claude/commands/TechieFlow/agents/");
603
643
  copyTree(dryRun ? join(sourceRoot, ".tfcore", "agents") : join(target, ".tfcore", "agents"), join(target, ".claude", "commands", "TechieFlow", "agents"));
604
644
  // 4. reference files at the root, only if missing
605
- copyFile(join(sourceRoot, "WORKFLOW.html"), join(target, "WORKFLOW.html"), { onlyIfMissing: true });
606
645
  copyFile(join(sourceRoot, "opencode.jsonc"), join(target, "opencode.jsonc"), { onlyIfMissing: true });
607
646
  // 4b. OpenCode bridge, always refreshed
608
647
  deployOpenCodeBridge();
609
- // 4c. Codex adapter
610
- deployCodexAdapter();
611
- if (!dryRun) say(" Codex adapter installed — trust this repository and review /hooks before relying on guards");
648
+ // 4c. files earlier versions deployed and the framework no longer ships
649
+ removeRetiredFiles();
612
650
  // 5. Claude Code permissions, only if missing
613
651
  if (writeText(join(target, ".claude", "settings.json"), `${canonicalSettings}\n`, { onlyIfMissing: true })) { if (!dryRun) say(" created .claude/settings.json"); }
614
652
  else say(" .claude/settings.json already exists — preserved");
@@ -624,6 +662,8 @@ async function install() {
624
662
  shimLegacyPersonas({ gapOnly: false });
625
663
  // 9. .gitignore, build-output audit, telemetry, .gitattributes
626
664
  deployHousekeeping();
665
+ // 10. leftovers of a plain `npm install` of this package, if any
666
+ tidyDependencyInstall();
627
667
 
628
668
  say("");
629
669
  if (dryRun) { say("✔ Dry run complete — no files changed."); return; }
@@ -631,13 +671,15 @@ async function install() {
631
671
  say("");
632
672
  say(`Next: cd "${target}"`);
633
673
  if (greenfield) say(" dotnet new sln + blazor + add TrBlazeUI/TechieRag NuGets + dotnet build");
634
- say(" open WORKFLOW.html in a browser");
635
674
  say(` start Claude Code or OpenCode and run the ${greenfield ? "greenfield" : "brownfield"} day-1 command from .tf-scaffold-note.txt`);
636
675
  }
637
676
 
638
677
  // ---------------------------------------------------------------- update (update-framework.sh)
639
678
 
640
- const frameworkSubdirs = ["agents", "tasks", "telemetry", "templates", "checklists", "data", "utils", "hooks", "workflows", "agent-teams"];
679
+ // Keep this in step with FRAMEWORK_SUBDIRS in update-framework.sh. `standards` arrived with the
680
+ // document schemas and was missing here, so a project migrated from the old layout came out
681
+ // without its coding standards (found by scripts/test-install.mjs, 2026-09-07).
682
+ const frameworkSubdirs = ["agents", "tasks", "telemetry", "templates", "checklists", "data", "utils", "hooks", "standards", "workflows", "agent-teams"];
641
683
  const frameworkTopFiles = ["enhanced-ide-development-workflow.md", "user-guide.md", "working-in-the-brownfield.md", "install-manifest.yaml", "TOKEN-GUIDE.md"];
642
684
  const legacyConfigPattern = /\.bmad-core|bmad-(master|orchestrator|analyst|architect|verifier)|BMad:/;
643
685
 
@@ -847,22 +889,18 @@ function update() {
847
889
  deployOpenCodeBridge();
848
890
  refreshRootOpenCodeConfig();
849
891
 
850
- // 4b. Codex adapter
851
- deployCodexAdapter();
852
- if (!dryRun) say(" Codex hooks changed or installed — trust this repository and review /hooks");
892
+ // 4b. files earlier versions deployed and the framework no longer ships
893
+ removeRetiredFiles();
853
894
  for (const file of libraryPersonas) if (existsSync(join(target, ".opencode", "command", file))) say(` .opencode/command/${file} — preserved (NuGet-deployed)`);
854
895
 
855
- // 4. WORKFLOW.html, always refreshed
856
- if (existsSync(join(sourceRoot, "WORKFLOW.html"))) {
857
- copyFile(join(sourceRoot, "WORKFLOW.html"), join(target, "WORKFLOW.html"));
858
- say(" WORKFLOW.html");
859
- }
860
896
  // 5. NuGet persona shims: gap-fill only, never an overwrite
861
897
  shimLegacyPersonas({ gapOnly: true });
862
898
  // 6, 7. messages about stale references in files the owner owns
863
899
  reportStaleReferences();
864
900
  // 8. .gitignore, build-output audit, telemetry, .gitattributes
865
901
  deployHousekeeping();
902
+ // 9. leftovers of a plain `npm install` of this package, if any
903
+ tidyDependencyInstall();
866
904
 
867
905
  say("");
868
906
  if (dryRun) {
@@ -903,29 +941,18 @@ function uninstall() {
903
941
  plan(".opencode/opencode.jsonc");
904
942
  plan(".opencode/opencode.json", "(generated by routing)");
905
943
  for (const file of shippedFiles(".opencode/command", ".md")) if (!libraryPersonas.has(file)) plan(`.opencode/command/${file}`);
906
- plan(".codex/hooks.json");
907
- plan(".codex/rules/techieflow.rules");
908
- if (existsSync(join(target, ".codex", "config.toml"))) {
909
- if (sameAsShipped(".codex/config.toml", ".codex/config.toml")) plan(".codex/config.toml");
910
- else keep(".codex/config.toml", "you changed it");
911
- }
912
- const codexAgents = join(target, ".codex", "agents");
913
- if (existsSync(codexAgents)) for (const f of readdirSync(codexAgents)) {
914
- if (f.endsWith(".toml") && /description = "TechieFlow [a-z-]+ (specialist|role)\."/.test(read(join(codexAgents, f)))) plan(`.codex/agents/${f}`);
915
- else keep(`.codex/agents/${f}`, "not written by the framework");
916
- }
917
- const skills = join(target, ".agents", "skills");
918
- if (existsSync(skills)) for (const f of readdirSync(skills)) if (f.startsWith("techieflow-")) plan(`.agents/skills/${f}`);
944
+ // Retired on 2026-09-07 but still present in a project set up by an older version.
945
+ for (const path of [".codex", ".agents/skills"]) if (existsSync(join(target, path))) plan(path, "(retired Codex adapter)");
919
946
  for (const lib of [".trblazeui", ".techierag"]) {
920
947
  if (!existsSync(join(target, lib))) continue;
921
948
  const rest = readdirSync(join(target, lib)).filter((f) => f !== ".codex-agent-package-owned" && f !== ".gitignore");
922
949
  plan(`${lib}/.codex-agent-package-owned`);
923
950
  if (rest.length === 0) plan(`${lib}/.gitignore`);
924
951
  }
925
- for (const [path, shipped] of [["WORKFLOW.html", "WORKFLOW.html"], ["opencode.jsonc", "opencode.jsonc"]]) {
926
- if (!existsSync(join(target, path))) continue;
927
- if (sameAsShipped(path, shipped)) plan(path);
928
- else keep(path, "you changed it");
952
+ if (existsSync(join(target, "WORKFLOW.html"))) plan("WORKFLOW.html", "(retired human workflow guide)");
953
+ if (existsSync(join(target, "opencode.jsonc"))) {
954
+ if (sameAsShipped("opencode.jsonc", "opencode.jsonc")) plan("opencode.jsonc");
955
+ else keep("opencode.jsonc", "you changed it");
929
956
  }
930
957
  plan(".tf-scaffold-note.txt");
931
958
  const hook = join(target, ".git", "hooks", "pre-commit");
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ // scripts/npm-cleanup.mjs — removes the package's own footprint after a plain `npm install`.
3
+ //
4
+ // The framework is not an application dependency. `npm install @techierathore/techieflow`
5
+ // still puts the package under node_modules/ and writes it into package.json and
6
+ // package-lock.json. Once the framework files are in place, this removes that footprint:
7
+ //
8
+ // node_modules/@techierathore/techieflow the package folder
9
+ // node_modules/@techierathore when nothing else is in it
10
+ // node_modules/.bin/techieflow* the command shims
11
+ // the package's entry in package.json, package-lock.json and node_modules/.package-lock.json
12
+ //
13
+ // If npm created package.json, package-lock.json or node_modules only for this install
14
+ // (an empty folder, or a .NET project), they are removed as well. A project's own
15
+ // package.json, lock file and node_modules are kept, minus the entry for this package.
16
+ //
17
+ // Used two ways:
18
+ // - by npm-postinstall.mjs, detached, after the install hook has deployed the framework.
19
+ // npm writes package.json, package-lock.json and node_modules/.package-lock.json AFTER
20
+ // the hook has run, so the detached process waits for that last write before it starts.
21
+ // - by install.mjs, straight away, when the package is found under node_modules/ of the
22
+ // project it just installed into. That happens when the owner ran `npm install` on an
23
+ // npm that skips install hooks (npm 12 and newer) and then ran the real command.
24
+
25
+ import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
26
+ import { join } from "node:path";
27
+ import { pathToFileURL } from "node:url";
28
+
29
+ export const packageName = "@techierathore/techieflow";
30
+ const dependencyKeys = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
31
+ const lockKey = `node_modules/${packageName}`;
32
+
33
+ function readJson(path) {
34
+ try { return JSON.parse(readFileSync(path, "utf8")); } catch { return null; }
35
+ }
36
+
37
+ const writeJson = (path, value) => writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
38
+
39
+ // True for a package.json that npm created for this install alone: nothing in it except
40
+ // empty dependency tables once this package's entry is gone.
41
+ function isEmptyManifest(manifest) {
42
+ return Object.entries(manifest).every(([key, value]) =>
43
+ dependencyKeys.includes(key) && value && typeof value === "object" && Object.keys(value).length === 0);
44
+ }
45
+
46
+ // Drops this package from a package.json object. Returns true when something was removed.
47
+ function dropFromManifest(manifest) {
48
+ let changed = false;
49
+ for (const key of dependencyKeys) {
50
+ if (manifest?.[key] && packageName in manifest[key]) { delete manifest[key][packageName]; changed = true; }
51
+ }
52
+ return changed;
53
+ }
54
+
55
+ // Drops this package from a package-lock.json object (root file or the hidden one in node_modules).
56
+ function dropFromLock(lock) {
57
+ let changed = false;
58
+ if (lock?.packages) {
59
+ if (lockKey in lock.packages) { delete lock.packages[lockKey]; changed = true; }
60
+ if (lock.packages[""] && dropFromManifest(lock.packages[""])) changed = true;
61
+ }
62
+ if (lock?.dependencies && packageName in lock.dependencies) { delete lock.dependencies[packageName]; changed = true; }
63
+ return changed;
64
+ }
65
+
66
+ // Does this project folder hold the package as an npm dependency artifact?
67
+ export function hasDependencyFootprint(target) {
68
+ const manifest = readJson(join(target, "package.json"));
69
+ return existsSync(join(target, "node_modules", "@techierathore", "techieflow"))
70
+ || dependencyKeys.some((key) => manifest?.[key] && packageName in manifest[key]);
71
+ }
72
+
73
+ // Removes the footprint. Returns the list of things removed, in plain words, for the caller to print.
74
+ export function removeDependencyFootprint(target) {
75
+ const removed = [];
76
+ const nm = join(target, "node_modules");
77
+ const scopeDir = join(nm, "@techierathore");
78
+ const packageDir = join(scopeDir, "techieflow");
79
+ const bin = join(nm, ".bin");
80
+
81
+ if (existsSync(packageDir)) { rmSync(packageDir, { recursive: true, force: true }); removed.push("node_modules/@techierathore/techieflow/"); }
82
+ if (existsSync(scopeDir) && readdirSync(scopeDir).every((e) => e === ".DS_Store")) rmSync(scopeDir, { recursive: true, force: true });
83
+ if (existsSync(bin)) {
84
+ for (const shim of readdirSync(bin)) {
85
+ if (shim === "techieflow" || shim.startsWith("techieflow.")) { rmSync(join(bin, shim), { force: true }); removed.push(`node_modules/.bin/${shim}`); }
86
+ }
87
+ }
88
+
89
+ const manifestPath = join(target, "package.json");
90
+ const manifest = readJson(manifestPath);
91
+ let manifestRemoved = false;
92
+ if (manifest && dropFromManifest(manifest)) {
93
+ if (isEmptyManifest(manifest)) { rmSync(manifestPath, { force: true }); manifestRemoved = true; removed.push("package.json (npm created it for this install)"); }
94
+ else { writeJson(manifestPath, manifest); removed.push(`the ${packageName} entry in package.json`); }
95
+ }
96
+
97
+ const lockPath = join(target, "package-lock.json");
98
+ if (manifestRemoved) {
99
+ if (existsSync(lockPath)) { rmSync(lockPath, { force: true }); removed.push("package-lock.json (npm created it for this install)"); }
100
+ } else {
101
+ const lock = readJson(lockPath);
102
+ if (lock && dropFromLock(lock)) { writeJson(lockPath, lock); removed.push(`the ${packageName} entry in package-lock.json`); }
103
+ }
104
+
105
+ if (existsSync(nm)) {
106
+ const bookkeeping = (entry) => entry === ".package-lock.json" || entry === ".DS_Store"
107
+ || (entry === ".bin" && readdirSync(bin).every((e) => e === ".DS_Store"));
108
+ if (readdirSync(nm).every(bookkeeping)) { rmSync(nm, { recursive: true, force: true }); removed.push("node_modules/ (npm created it for this install)"); }
109
+ else {
110
+ const hiddenPath = join(nm, ".package-lock.json");
111
+ const hidden = readJson(hiddenPath);
112
+ if (hidden && dropFromLock(hidden)) writeJson(hiddenPath, hidden);
113
+ }
114
+ }
115
+ return removed;
116
+ }
117
+
118
+ // ---- standalone use: node npm-cleanup.mjs <project folder>, started detached by the install hook.
119
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
120
+ const target = process.argv[2];
121
+ if (!target || !existsSync(target)) process.exit(0);
122
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
123
+ const hiddenLock = join(target, "node_modules", ".package-lock.json");
124
+ // npm's last write is node_modules/.package-lock.json. Wait for it to name this package,
125
+ // then a moment longer for npm to close it. Give up waiting after 60 seconds (for example
126
+ // when npm was told not to write a lock file) and clean up anyway.
127
+ for (let waited = 0; waited < 60_000; waited += 200) {
128
+ const hidden = readJson(hiddenLock);
129
+ if (hidden?.packages && lockKey in hidden.packages) break;
130
+ await sleep(200);
131
+ }
132
+ await sleep(500);
133
+ // On Windows npm may still hold a handle for a moment; retry briefly.
134
+ for (let attempt = 0; attempt < 50; attempt++) {
135
+ try { removeDependencyFootprint(target); break; } catch { await sleep(200); }
136
+ }
137
+ }