@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.
- package/.claude/commands/TechieFlow/agents/analyst.md +0 -5
- package/.claude/commands/TechieFlow/agents/architect.md +0 -6
- package/.claude/commands/TechieFlow/agents/flow-master.md +41 -215
- package/.claude/commands/TechieFlow/agents/verifier.md +23 -64
- package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
- package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
- package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
- package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +20 -143
- package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
- package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
- package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
- package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
- package/.claude/commands/TechieFlow/tasks/metrics-report.md +1 -1
- package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
- package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
- package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
- package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
- package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
- package/.opencode/plugin/techieflow.js +39 -12
- package/.tfcore/agents/analyst.md +0 -5
- package/.tfcore/agents/architect.md +0 -6
- package/.tfcore/agents/flow-master.md +41 -215
- package/.tfcore/agents/verifier.md +23 -64
- package/.tfcore/core-config.yaml +10 -0
- package/.tfcore/hooks/block-git.sh +4 -4
- package/.tfcore/hooks/guard-artifacts.sh +2 -2
- package/.tfcore/hooks/guard-build.sh +76 -0
- package/.tfcore/hooks/guard-db.sh +90 -0
- package/.tfcore/hooks/guard-metrics.sh +56 -0
- package/.tfcore/hooks/guard-status-html.sh +116 -71
- package/.tfcore/hooks/guard-status.sh +43 -16
- package/.tfcore/hooks/guard-verify.sh +70 -48
- package/.tfcore/hooks/sweep-artifacts.sh +2 -2
- package/.tfcore/install-manifest.yaml +0 -18
- package/.tfcore/routing.yaml +25 -14
- package/.tfcore/standards/coding-standards-core.md +61 -0
- package/.tfcore/standards/coding-standards-dotnet.md +93 -0
- package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
- package/.tfcore/tasks/_smoke-test-policy.md +20 -84
- package/.tfcore/tasks/_status-update-gate.md +10 -74
- package/.tfcore/tasks/_yolo-mode.md +20 -58
- package/.tfcore/tasks/amend-docs.md +20 -143
- package/.tfcore/tasks/build-phase.md +14 -224
- package/.tfcore/tasks/day1-brownfield.md +31 -505
- package/.tfcore/tasks/day1-greenfield.md +28 -213
- package/.tfcore/tasks/deploy-checklist.md +19 -0
- package/.tfcore/tasks/devguide.md +10 -219
- package/.tfcore/tasks/fix-issues.md +10 -129
- package/.tfcore/tasks/log-miss.md +19 -158
- package/.tfcore/tasks/metrics-report.md +1 -1
- package/.tfcore/tasks/mockups.md +12 -117
- package/.tfcore/tasks/refresh-status.md +9 -130
- package/.tfcore/tasks/split-brd.md +10 -126
- package/.tfcore/tasks/triage-and-fix.md +14 -0
- package/.tfcore/tasks/triage-issues.md +11 -152
- package/.tfcore/tasks/verify-phase.md +36 -572
- package/.tfcore/telemetry/SCHEMA.md +43 -7
- package/.tfcore/telemetry/tf-metrics.sh +152 -17
- package/.tfcore/templates/project-brief-tmpl.yaml +1 -1
- package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
- package/.tfcore/templates/stack-questions.md +77 -0
- package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
- package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
- package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
- package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
- package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
- package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
- package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
- package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
- package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
- package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
- package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
- package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
- package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
- package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
- package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
- package/.tfcore/user-guide.md +0 -23
- package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
- package/.tfcore/utils/tf-brd-status.py +176 -0
- package/.tfcore/utils/tf-brd-status.sh +19 -0
- package/.tfcore/utils/tf-build-list.py +191 -0
- package/.tfcore/utils/tf-build-list.sh +20 -0
- package/.tfcore/utils/tf-build.sh +143 -0
- package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
- package/.tfcore/utils/tf-checklist-edit.py +133 -0
- package/.tfcore/utils/tf-day1-files.py +208 -0
- package/.tfcore/utils/tf-day1-files.sh +20 -0
- package/.tfcore/utils/tf-devguide-list.py +249 -0
- package/.tfcore/utils/tf-devguide-list.sh +18 -0
- package/.tfcore/utils/tf-doc-check.py +1267 -0
- package/.tfcore/utils/tf-doc-check.sh +29 -0
- package/.tfcore/utils/tf-emit.sh +203 -13
- package/.tfcore/utils/tf-fix-close.sh +83 -0
- package/.tfcore/utils/tf-goal.sh +193 -62
- package/.tfcore/utils/tf-harness.sh +4 -9
- package/.tfcore/utils/tf-log-miss.py +197 -0
- package/.tfcore/utils/tf-log-miss.sh +14 -0
- package/.tfcore/utils/tf-misses-md.py +211 -0
- package/.tfcore/utils/tf-misses-md.sh +13 -0
- package/.tfcore/utils/tf-mockups-locate.py +66 -0
- package/.tfcore/utils/tf-mockups-locate.sh +18 -0
- package/.tfcore/utils/tf-opencode-audit.py +23 -7
- package/.tfcore/utils/tf-perf-grade.py +97 -0
- package/.tfcore/utils/tf-perf-grade.sh +12 -0
- package/.tfcore/utils/tf-phase.sh +74 -0
- package/.tfcore/utils/tf-routing-bind.sh +10 -5
- package/.tfcore/utils/tf-routing.sh +6 -7
- package/.tfcore/utils/tf-split-brd.py +304 -0
- package/.tfcore/utils/tf-split-brd.sh +22 -0
- package/.tfcore/utils/tf-status-evidence.sh +68 -0
- package/.tfcore/utils/tf-status-facts.py +309 -0
- package/.tfcore/utils/tf-status-facts.sh +20 -0
- package/.tfcore/utils/tf-triage.py +243 -0
- package/.tfcore/utils/tf-triage.sh +16 -0
- package/.tfcore/utils/tf-verify-boot.sh +250 -0
- package/.tfcore/utils/tf-verify-emit.sh +110 -0
- package/.tfcore/utils/tf-verify-env.sh +88 -0
- package/.tfcore/utils/tf-verify-list.py +270 -0
- package/.tfcore/utils/tf-verify-list.sh +13 -0
- package/.tfcore/utils/tf-verify-screens.mjs +283 -0
- package/.tfcore/utils/tf-verify-screens.sh +22 -0
- package/.tfcore/utils/tf-verify-tests.sh +123 -0
- package/.tfcore/utils/tf-verify-verdict.py +283 -0
- package/.tfcore/utils/tf-verify-verdict.sh +15 -0
- package/.tfcore/utils/tf-yolo.sh +59 -0
- package/README.md +114 -1149
- package/docs/TechieFlow-Installation.md +27 -7
- package/opencode.jsonc +13 -33
- package/package.json +4 -7
- package/scripts/install.mjs +73 -46
- package/scripts/npm-cleanup.mjs +137 -0
- package/scripts/npm-postinstall.mjs +49 -0
- package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
- package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
- package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
- package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
- package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
- package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
- package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
- package/.codex/config.toml +0 -46
- package/.codex/hooks.json +0 -63
- package/.codex/rules/techieflow.rules +0 -8
- package/.tfcore/hooks/codex-adapter.py +0 -248
- package/.tfcore/tasks/advanced-elicitation.md +0 -117
- package/.tfcore/tasks/author-brd.md +0 -79
- package/.tfcore/tasks/document-project.md +0 -343
- package/.tfcore/tasks/execute-checklist.md +0 -86
- package/.tfcore/tasks/index-docs.md +0 -173
- package/.tfcore/tasks/kb-mode-interaction.md +0 -75
- package/.tfcore/tasks/shard-doc.md +0 -185
- package/.tfcore/utils/tf-codex-bind.py +0 -196
- package/.tfcore/utils/tf-codex-telemetry.py +0 -60
- package/WORKFLOW.html +0 -1986
- package/scripts/test-install.mjs +0 -505
- 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
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
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": "
|
|
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
|
|
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": "
|
|
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": "
|
|
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.
|
|
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/
|
|
43
|
-
"scripts/
|
|
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",
|
package/scripts/install.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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/", "
|
|
224
|
-
patterns: [/^\/?\.tfcore\/?$/, /^\/?\.claude\/?$/, /^\/?\.opencode\/?$/,
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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.
|
|
610
|
-
|
|
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
|
-
|
|
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.
|
|
851
|
-
|
|
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
|
-
|
|
907
|
-
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
if (sameAsShipped(
|
|
928
|
-
else keep(
|
|
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
|
+
}
|