@fernado03/zoo-flow 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -90
- package/bin/zoo-flow.js +405 -56
- package/docs/architecture.md +380 -0
- package/docs/bloat-control.md +49 -0
- package/docs/command-design.md +38 -0
- package/docs/command-flow.md +133 -0
- package/docs/comparison.md +86 -0
- package/docs/context-packs.md +35 -0
- package/docs/dogfood/01-small-library.md +28 -0
- package/docs/dogfood/02-web-app.md +29 -0
- package/docs/dogfood/03-mixed-monorepo.md +29 -0
- package/docs/mode-rules.md +86 -0
- package/docs/npm-publishing.md +79 -0
- package/docs/out-of-scope/mainstream-issue-trackers-only.md +25 -0
- package/docs/out-of-scope/question-limits.md +18 -0
- package/docs/out-of-scope/setup-skill-verify-mode.md +15 -0
- package/docs/overview.md +61 -0
- package/docs/philosophy.md +73 -0
- package/docs/quality-scorecard.md +23 -0
- package/docs/skill-maintenance.md +32 -0
- package/docs/skills-index.md +61 -0
- package/docs/team-mode.md +46 -0
- package/docs/token-budget.md +22 -0
- package/docs/troubleshooting.md +288 -0
- package/examples/demo-transcripts/01-small-tweak.md +37 -0
- package/examples/demo-transcripts/02-unknown-bug-fix.md +37 -0
- package/examples/demo-transcripts/03-new-feature.md +37 -0
- package/examples/demo-transcripts/04-refactor.md +37 -0
- package/examples/demo-transcripts/05-review-and-verify.md +37 -0
- package/examples/feature-flow.md +117 -0
- package/examples/fix-flow.md +139 -0
- package/package.json +16 -5
- package/quality/scorecard.json +88 -0
- package/quality/token-budget.exceptions.json +13 -0
- package/scripts/bundle.ps1 +135 -0
- package/scripts/check-golden-transcripts.js +69 -0
- package/scripts/check-package-links.js +72 -0
- package/scripts/check-package-manifest.js +70 -0
- package/scripts/eval-routing.js +149 -0
- package/scripts/score-quality.js +292 -0
- package/scripts/test-doctor.js +107 -0
- package/scripts/test-project-shapes.js +99 -0
- package/scripts/token-budget.js +105 -0
- package/templates/full/.roo/commands/caveman.md +1 -1
- package/templates/full/.roo/commands/diagnose.md +2 -1
- package/templates/full/.roo/commands/explore.md +13 -13
- package/templates/full/.roo/commands/feature.md +1 -1
- package/templates/full/.roo/commands/fix.md +1 -1
- package/templates/full/.roo/commands/grill-me.md +2 -1
- package/templates/full/.roo/commands/grill-with-docs.md +2 -1
- package/templates/full/.roo/commands/handoff.md +2 -1
- package/templates/full/.roo/commands/improve-codebase-architecture.md +2 -1
- package/templates/full/.roo/commands/prototype.md +1 -1
- package/templates/full/.roo/commands/refactor.md +1 -1
- package/templates/full/.roo/commands/review.md +11 -0
- package/templates/full/.roo/commands/scaffold-context.md +13 -13
- package/templates/full/.roo/commands/setup-matt-pocock-skills.md +8 -8
- package/templates/full/.roo/commands/tdd.md +1 -1
- package/templates/full/.roo/commands/to-issues.md +2 -1
- package/templates/full/.roo/commands/to-prd.md +2 -1
- package/templates/full/.roo/commands/triage.md +1 -1
- package/templates/full/.roo/commands/tweak.md +1 -1
- package/templates/full/.roo/commands/update-docs.md +22 -22
- package/templates/full/.roo/commands/verify.md +11 -0
- package/templates/full/.roo/commands/write-a-skill.md +2 -1
- package/templates/full/.roo/commands/zoom-out.md +2 -1
- package/templates/full/.roo/rules/01-command-protocol.md +1 -1
- package/templates/full/.roo/rules/04-context-economy.md +27 -29
- package/templates/full/.roo/rules-code-tweaker/01-completion.md +12 -8
- package/templates/full/.roo/rules-custom-orchestrator/00-routing.md +77 -63
- package/templates/full/.roo/rules-custom-orchestrator/01-delegation-message.md +59 -55
- package/templates/full/.roo/rules-system-architect/02-completion.md +6 -2
- package/templates/full/.roo/skills/engineering/README.md +2 -0
- package/templates/full/.roo/skills/engineering/commit-and-document/SKILL.md +1 -2
- package/templates/full/.roo/skills/engineering/grill-with-docs/ADR-FORMAT.md +1 -1
- package/templates/full/.roo/skills/engineering/grill-with-docs/CONTEXT-FORMAT.md +36 -61
- package/templates/full/.roo/skills/engineering/grill-with-docs/SKILL.md +1 -1
- package/templates/full/.roo/skills/engineering/improve-codebase-architecture/SKILL.md +3 -3
- package/templates/full/.roo/skills/engineering/prototype/SKILL.md +37 -37
- package/templates/full/.roo/skills/engineering/review/SKILL.md +111 -0
- package/templates/full/.roo/skills/engineering/scaffold-context/SKILL.md +218 -152
- package/templates/full/.roo/skills/engineering/scaffold-context/templates/writing-patterns.md +17 -0
- package/templates/full/.roo/skills/engineering/setup-matt-pocock-skills/SKILL.md +3 -3
- package/templates/full/.roo/skills/engineering/setup-matt-pocock-skills/domain.md +2 -3
- package/templates/full/.roo/skills/engineering/tdd/SKILL.md +2 -0
- package/templates/full/.roo/skills/engineering/to-prd/SKILL.md +57 -57
- package/templates/full/.roo/skills/engineering/tweak/SKILL.md +2 -1
- package/templates/full/.roo/skills/engineering/verify/SKILL.md +80 -0
- package/templates/full/.roo/skills/in-progress/README.md +0 -1
- package/templates/full/.roomodes +47 -47
- package/templates/full/.zoo-flow/CONTEXT.md +8 -8
- package/templates/full/.zoo-flow/START_HERE.md +61 -61
- package/templates/full/.zoo-flow/docs/adr/0001-record-architecture-decisions.md +22 -22
- package/templates/full/.zoo-flow/evals/no-regression-checklist.md +26 -24
- package/templates/full/.zoo-flow/evals/routing-cases.jsonl +20 -0
- package/templates/full/.zoo-flow/evals/routing-cases.md +213 -189
- package/templates/full/.zoo-flow/project-profile.json +24 -0
- package/tests/fixtures/bad-routing-cases/bad-json.jsonl +1 -0
- package/tests/fixtures/bad-routing-cases/bad-mode.jsonl +1 -0
- package/tests/fixtures/bad-routing-cases/missing-command.jsonl +1 -0
- package/tests/fixtures/doctor/bad-built-in-delegation/fixture.json +1 -0
- package/tests/fixtures/doctor/bad-mode-slug/fixture.json +1 -0
- package/tests/fixtures/doctor/bad-skill-wrapper/fixture.json +1 -0
- package/tests/fixtures/doctor/bad-zoo-path/fixture.json +1 -0
- package/tests/fixtures/doctor/helper-missing-mode/fixture.json +1 -0
- package/tests/fixtures/doctor/helper-not-permitted/fixture.json +1 -0
- package/tests/fixtures/doctor/manual-good-template/fixture.json +1 -0
- package/tests/fixtures/doctor/missing-command/fixture.json +1 -0
- package/tests/fixtures/doctor/missing-roomodes/fixture.json +1 -0
- package/tests/fixtures/doctor/missing-skill/fixture.json +1 -0
- package/tests/fixtures/project-shapes/cli-tool/cmd/root.go +1 -0
- package/tests/fixtures/project-shapes/cli-tool/fixture.json +1 -0
- package/tests/fixtures/project-shapes/cli-tool/package.json +1 -0
- package/tests/fixtures/project-shapes/data-pipeline/fixture.json +1 -0
- package/tests/fixtures/project-shapes/data-pipeline/pipelines/invoices.py +1 -0
- package/tests/fixtures/project-shapes/data-pipeline/pyproject.toml +2 -0
- package/tests/fixtures/project-shapes/library/fixture.json +1 -0
- package/tests/fixtures/project-shapes/library/package.json +1 -0
- package/tests/fixtures/project-shapes/library/src/index.ts +1 -0
- package/tests/fixtures/project-shapes/monorepo/fixture.json +1 -0
- package/tests/fixtures/project-shapes/monorepo/package.json +1 -0
- package/tests/fixtures/project-shapes/monorepo/packages/core/index.ts +1 -0
- package/tests/fixtures/project-shapes/monorepo/packages/web/index.ts +1 -0
- package/tests/fixtures/project-shapes/serverless/fixture.json +1 -0
- package/tests/fixtures/project-shapes/serverless/functions/webhook.ts +1 -0
- package/tests/fixtures/project-shapes/serverless/package.json +1 -0
- package/tests/fixtures/project-shapes/web-app/app/routes/index.tsx +1 -0
- package/tests/fixtures/project-shapes/web-app/fixture.json +1 -0
- package/tests/fixtures/project-shapes/web-app/package.json +1 -0
- package/tests/golden-transcripts/01-small-tweak-golden.md +21 -0
- package/tests/golden-transcripts/02-diagnosis-golden.md +26 -0
- package/tests/golden-transcripts/03-verification-golden.md +24 -0
- package/tests/golden-transcripts/04-review-golden.md +26 -0
- package/tests/golden-transcripts/05-feature-planning-golden.md +23 -0
- package/templates/full/.roo/skills/in-progress/review/SKILL.md +0 -39
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Example: `/fix` flow
|
|
2
|
+
|
|
3
|
+
A worked example of the multi-phase `/fix` chain: orchestrator delegates
|
|
4
|
+
to the architect, architect diagnoses, architect switches to the tweaker
|
|
5
|
+
to implement, tweaker switches back, architect runs the post-mortem,
|
|
6
|
+
tweaker prepares the commit. The whole flow has explicit HITL stops.
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
- Active mode: `🪃 Custom Orchestrator`.
|
|
11
|
+
- Workspace has a real bug to fix. For this example, assume:
|
|
12
|
+
|
|
13
|
+
> "The login button does nothing on the second click. First click
|
|
14
|
+
> works."
|
|
15
|
+
|
|
16
|
+
## Phase 1 — orchestrator routes
|
|
17
|
+
|
|
18
|
+
Type:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
/fix The login button does nothing on the second click. First click works.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Expected**
|
|
25
|
+
|
|
26
|
+
Orchestrator looks up the routing matrix and delegates with `new_task`
|
|
27
|
+
targeting `system-architect`. The delegated message includes the
|
|
28
|
+
slash form, user context, proceed policy, command-protocol pointer,
|
|
29
|
+
skills location, and the completion rule.
|
|
30
|
+
|
|
31
|
+
## Phase 2 — architect diagnoses
|
|
32
|
+
|
|
33
|
+
The new task window opens in `🏗️ System Architect`. The architect:
|
|
34
|
+
|
|
35
|
+
1. Loads `/fix` per the command protocol.
|
|
36
|
+
2. Runs the `diagnose` skill, phases 1–3.
|
|
37
|
+
3. Produces a short list of hypotheses.
|
|
38
|
+
4. **Halts** and asks you to pick one.
|
|
39
|
+
|
|
40
|
+
**Expected message (paraphrased)**
|
|
41
|
+
|
|
42
|
+
> Hypotheses:
|
|
43
|
+
> 1. Click handler is wired once and never re-bound after a state change.
|
|
44
|
+
> 2. The button enters a disabled state on first click and never resets.
|
|
45
|
+
> 3. A queued network request is canceling the second click's handler.
|
|
46
|
+
>
|
|
47
|
+
> Which would you like to instrument? (1 / 2 / 3)
|
|
48
|
+
|
|
49
|
+
You answer:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
2
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Phase 3 — architect instruments
|
|
56
|
+
|
|
57
|
+
The architect runs phase 4 of the `diagnose` skill on hypothesis 2.
|
|
58
|
+
This phase may involve reading state-management code, adding a
|
|
59
|
+
console-log or instrumentation suggestion (Markdown only — the architect
|
|
60
|
+
cannot edit source), and producing a confirmed root cause.
|
|
61
|
+
|
|
62
|
+
**Expected**
|
|
63
|
+
|
|
64
|
+
> Root cause confirmed: `LoginButton` sets `disabled` on the first
|
|
65
|
+
> click and the `onSuccess` handler does not reset it. The second
|
|
66
|
+
> click is being absorbed by the disabled state.
|
|
67
|
+
|
|
68
|
+
The architect summarizes the proposed fix and prepares to hand off.
|
|
69
|
+
|
|
70
|
+
## Phase 4 — architect switches to tweaker
|
|
71
|
+
|
|
72
|
+
The architect calls `switch_mode` to `code-tweaker` **inside the same
|
|
73
|
+
task window** with a summary of:
|
|
74
|
+
|
|
75
|
+
- The root cause.
|
|
76
|
+
- The proposed fix (`onSuccess` resets `disabled`).
|
|
77
|
+
- The files involved.
|
|
78
|
+
- The tests to add or extend.
|
|
79
|
+
|
|
80
|
+
The tweaker takes over without losing context.
|
|
81
|
+
|
|
82
|
+
## Phase 5 — tweaker implements
|
|
83
|
+
|
|
84
|
+
The tweaker:
|
|
85
|
+
|
|
86
|
+
1. Edits the `LoginButton` component to reset `disabled` on success
|
|
87
|
+
and on error.
|
|
88
|
+
2. Adds or extends tests covering the second-click case.
|
|
89
|
+
3. Runs the test suite.
|
|
90
|
+
4. Reports back.
|
|
91
|
+
|
|
92
|
+
**HITL stop**: the tweaker does not commit. Per the git rule, it waits
|
|
93
|
+
for your approval.
|
|
94
|
+
|
|
95
|
+
## Phase 6 — back to architect for post-mortem
|
|
96
|
+
|
|
97
|
+
The tweaker calls `switch_mode` back to `system-architect`. The
|
|
98
|
+
architect runs phase 6 of `diagnose` (post-mortem):
|
|
99
|
+
|
|
100
|
+
- What was the original assumption that masked the bug?
|
|
101
|
+
- Is this an isolated mistake or a pattern?
|
|
102
|
+
- If a pattern, would `/refactor` help?
|
|
103
|
+
|
|
104
|
+
The architect produces a short post-mortem note in `docs/` or
|
|
105
|
+
`.scratch/` (Markdown only) and either drops the matter there or
|
|
106
|
+
suggests `/refactor`.
|
|
107
|
+
|
|
108
|
+
## Phase 7 — tweaker prepares the commit
|
|
109
|
+
|
|
110
|
+
The architect switches back to the tweaker. The tweaker suggests
|
|
111
|
+
`/commit-and-document`. **The orchestrator does not auto-launch it.**
|
|
112
|
+
That command runs only when you type it.
|
|
113
|
+
|
|
114
|
+
## Phase 8 — return to orchestrator
|
|
115
|
+
|
|
116
|
+
When the chain is complete (or blocked), the active mode calls
|
|
117
|
+
`attempt_completion`. The orchestrator summarizes for you and halts.
|
|
118
|
+
|
|
119
|
+
## Pass criteria
|
|
120
|
+
|
|
121
|
+
- [ ] Orchestrator delegated to `system-architect`, not the tweaker.
|
|
122
|
+
- [ ] Architect halted after phase 3 hypotheses, did not instrument
|
|
123
|
+
until you picked one.
|
|
124
|
+
- [ ] Architect did not edit source code at any point.
|
|
125
|
+
- [ ] Architect used `switch_mode` (same window) to hand off, not
|
|
126
|
+
`new_task`.
|
|
127
|
+
- [ ] Tweaker did not run `git commit` or `git push` without your
|
|
128
|
+
explicit approval.
|
|
129
|
+
- [ ] After return, orchestrator halted instead of auto-launching
|
|
130
|
+
`/commit-and-document`.
|
|
131
|
+
|
|
132
|
+
## Common slips
|
|
133
|
+
|
|
134
|
+
- Architect tries to edit source: see
|
|
135
|
+
[`docs/troubleshooting.md`](../docs/troubleshooting.md#architect-trying-to-edit-source).
|
|
136
|
+
- Tweaker commits without approval: tighten the git rule in the
|
|
137
|
+
tweaker's `customInstructions`.
|
|
138
|
+
- Orchestrator launches `/commit-and-document` automatically: see
|
|
139
|
+
[`docs/troubleshooting.md`](../docs/troubleshooting.md#slash-command-leakage-from-subtask-summaries).
|
package/package.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fernado03/zoo-flow",
|
|
3
3
|
"description": "Workflow control plane for Zoo Code.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"zoo-flow": "bin/zoo-flow.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
+
"scripts",
|
|
11
12
|
"templates",
|
|
13
|
+
"tests",
|
|
14
|
+
"docs",
|
|
15
|
+
"examples",
|
|
16
|
+
"quality",
|
|
12
17
|
"README.md",
|
|
13
18
|
"LICENSE"
|
|
14
|
-
],
|
|
19
|
+
],
|
|
15
20
|
"license": "MIT",
|
|
16
21
|
"repository": {
|
|
17
22
|
"type": "git",
|
|
@@ -32,9 +37,15 @@
|
|
|
32
37
|
"agent-workflows"
|
|
33
38
|
],
|
|
34
39
|
"scripts": {
|
|
35
|
-
"check": "node bin/zoo-flow.js doctor --template-only",
|
|
36
|
-
"pack:check": "npm pack --dry-run"
|
|
37
|
-
|
|
40
|
+
"check": "node scripts/test-doctor.js && node bin/zoo-flow.js doctor --template-only && node scripts/eval-routing.js && node scripts/check-package-links.js",
|
|
41
|
+
"pack:check": "npm pack --dry-run",
|
|
42
|
+
"token-budget": "node scripts/token-budget.js",
|
|
43
|
+
"package-manifest": "node scripts/check-package-manifest.js",
|
|
44
|
+
"golden-transcripts": "node scripts/check-golden-transcripts.js",
|
|
45
|
+
"project-shapes": "node scripts/test-project-shapes.js",
|
|
46
|
+
"score-quality": "node scripts/score-quality.js",
|
|
47
|
+
"release:check": "npm run check && npm run token-budget && npm run package-manifest && npm run golden-transcripts && npm run project-shapes && npm run score-quality && npm run pack:check"
|
|
48
|
+
},
|
|
38
49
|
"engines": {
|
|
39
50
|
"node": ">=18"
|
|
40
51
|
},
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"minimum_score": 9.5,
|
|
4
|
+
"categories": {
|
|
5
|
+
"command_integrity": {
|
|
6
|
+
"weight": 0.20,
|
|
7
|
+
"checks": [
|
|
8
|
+
"All documented commands have mode declarations",
|
|
9
|
+
"All mode declarations match command-policy map",
|
|
10
|
+
"Global rules cover paths, protocol, failure, reply, context-economy",
|
|
11
|
+
"Mode rule folders exist for all three custom modes",
|
|
12
|
+
"Routing table matches command-policy map"
|
|
13
|
+
],
|
|
14
|
+
"max_score": 10.0
|
|
15
|
+
},
|
|
16
|
+
"skill_quality": {
|
|
17
|
+
"weight": 0.20,
|
|
18
|
+
"checks": [
|
|
19
|
+
"Every command referencing a skill uses canonical Skill: marker",
|
|
20
|
+
"Referenced skills exist on disk",
|
|
21
|
+
"Skills describe purpose, steps, and output format",
|
|
22
|
+
"No thin non-canonical skill wrappers exist",
|
|
23
|
+
"Skills follow bucket layout under .roo/skills/"
|
|
24
|
+
],
|
|
25
|
+
"max_score": 10.0
|
|
26
|
+
},
|
|
27
|
+
"validation_depth": {
|
|
28
|
+
"weight": 0.20,
|
|
29
|
+
"checks": [
|
|
30
|
+
"Doctor validates all required files exist",
|
|
31
|
+
"Doctor checks skill-reference integrity",
|
|
32
|
+
"Doctor checks mode slugs against allowed set",
|
|
33
|
+
"Doctor detects built-in delegation targets",
|
|
34
|
+
"Doctor checks .roomodes permits documented commands",
|
|
35
|
+
"Doctor validates routing table rows",
|
|
36
|
+
"Doctor detects old doc-name leakage",
|
|
37
|
+
"Doctor validates ADR path consistency"
|
|
38
|
+
],
|
|
39
|
+
"max_score": 10.0
|
|
40
|
+
},
|
|
41
|
+
"token_economy": {
|
|
42
|
+
"weight": 0.10,
|
|
43
|
+
"checks": [
|
|
44
|
+
"Context-economy rule exists and is referenced",
|
|
45
|
+
"Domain-doc read gate defers broad reads",
|
|
46
|
+
"Completion rules specify evidence output format",
|
|
47
|
+
"Orchestrator routes by risk level to lightest safe workflow"
|
|
48
|
+
],
|
|
49
|
+
"max_score": 10.0
|
|
50
|
+
},
|
|
51
|
+
"verification": {
|
|
52
|
+
"weight": 0.10,
|
|
53
|
+
"checks": [
|
|
54
|
+
"Verify command exists and references verify skill",
|
|
55
|
+
"Verify skill specifies evidence output format",
|
|
56
|
+
"Verify skill hard-codes no-claim-unless-run rule",
|
|
57
|
+
"Doctor fixture tests exist and pass",
|
|
58
|
+
"Routing eval cases exist and validate",
|
|
59
|
+
"Package-link integrity check exists"
|
|
60
|
+
],
|
|
61
|
+
"max_score": 10.0
|
|
62
|
+
},
|
|
63
|
+
"review_process": {
|
|
64
|
+
"weight": 0.10,
|
|
65
|
+
"checks": [
|
|
66
|
+
"Review command exists and references review skill",
|
|
67
|
+
"Review skill covers standards and spec axes",
|
|
68
|
+
"Review skill has Security/Risk axis",
|
|
69
|
+
"Review findings ordered by severity",
|
|
70
|
+
"Review result ends with one of four canonical results"
|
|
71
|
+
],
|
|
72
|
+
"max_score": 10.0
|
|
73
|
+
},
|
|
74
|
+
"release_readiness": {
|
|
75
|
+
"weight": 0.10,
|
|
76
|
+
"checks": [
|
|
77
|
+
"release:check script exists in package.json",
|
|
78
|
+
"release:check runs doctor, eval-routing, package-links",
|
|
79
|
+
"release:check runs package-manifest check",
|
|
80
|
+
"release:check runs token-budget check",
|
|
81
|
+
"release:check runs score-quality check",
|
|
82
|
+
"quality/scorecard.json defines minimum thresholds",
|
|
83
|
+
"npm pack --dry-run succeeds"
|
|
84
|
+
],
|
|
85
|
+
"max_score": 10.0
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Token budget exceptions. Keys are relative file paths from project root. Values are reason + override size.",
|
|
3
|
+
"overrides": {
|
|
4
|
+
"compiled.md": {
|
|
5
|
+
"reason": "Single-file bundle doc, expected to be large",
|
|
6
|
+
"max_bytes": 819200
|
|
7
|
+
},
|
|
8
|
+
"ZOO-FLOW-BUNDLE.md": {
|
|
9
|
+
"reason": "Single-file bundle doc, expected to be large",
|
|
10
|
+
"max_bytes": 819200
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Compile every git-tracked file in the repo into a single Markdown snapshot.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Walks `git ls-files`, embeds each file under a heading with its repo-relative
|
|
7
|
+
path, and writes the result to ZOO-FLOW-BUNDLE.md at the repo root.
|
|
8
|
+
|
|
9
|
+
Binary files and files larger than -MaxBytes are skipped with a placeholder.
|
|
10
|
+
Files containing triple backticks get four-backtick fences automatically so
|
|
11
|
+
nested code blocks render correctly.
|
|
12
|
+
|
|
13
|
+
The output is local-only and gitignored.
|
|
14
|
+
|
|
15
|
+
.PARAMETER OutFile
|
|
16
|
+
Output path. Defaults to ZOO-FLOW-BUNDLE.md at the repo root.
|
|
17
|
+
|
|
18
|
+
.PARAMETER MaxBytes
|
|
19
|
+
Files larger than this are skipped with a placeholder. Default 200000.
|
|
20
|
+
|
|
21
|
+
.EXAMPLE
|
|
22
|
+
pwsh ./scripts/bundle.ps1
|
|
23
|
+
pwsh ./scripts/bundle.ps1 -OutFile bundle.md -MaxBytes 500000
|
|
24
|
+
#>
|
|
25
|
+
|
|
26
|
+
[CmdletBinding()]
|
|
27
|
+
param(
|
|
28
|
+
[string] $OutFile,
|
|
29
|
+
[int] $MaxBytes = 200000
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
$ErrorActionPreference = "Stop"
|
|
33
|
+
|
|
34
|
+
$repoRoot = (& git rev-parse --show-toplevel).Trim()
|
|
35
|
+
if (-not $repoRoot) { throw "Not inside a git repository." }
|
|
36
|
+
|
|
37
|
+
if (-not $OutFile) {
|
|
38
|
+
$OutFile = Join-Path $repoRoot "ZOO-FLOW-BUNDLE.md"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
$files = & git -C $repoRoot ls-files
|
|
42
|
+
$commit = (& git -C $repoRoot rev-parse --short HEAD).Trim()
|
|
43
|
+
$branch = (& git -C $repoRoot branch --show-current).Trim()
|
|
44
|
+
|
|
45
|
+
$extLang = @{
|
|
46
|
+
".md" = "markdown"
|
|
47
|
+
".js" = "javascript"
|
|
48
|
+
".mjs" = "javascript"
|
|
49
|
+
".cjs" = "javascript"
|
|
50
|
+
".ts" = "typescript"
|
|
51
|
+
".json" = "json"
|
|
52
|
+
".sh" = "bash"
|
|
53
|
+
".ps1" = "powershell"
|
|
54
|
+
".yml" = "yaml"
|
|
55
|
+
".yaml" = "yaml"
|
|
56
|
+
".toml" = "toml"
|
|
57
|
+
".html" = "html"
|
|
58
|
+
".css" = "css"
|
|
59
|
+
".gitignore" = "gitignore"
|
|
60
|
+
".roomodes" = "json"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Test-IsBinary {
|
|
64
|
+
param([string] $Path)
|
|
65
|
+
try {
|
|
66
|
+
$bytes = [IO.File]::ReadAllBytes($Path)
|
|
67
|
+
$sample = [Math]::Min($bytes.Length, 8000)
|
|
68
|
+
for ($i = 0; $i -lt $sample; $i++) {
|
|
69
|
+
if ($bytes[$i] -eq 0) { return $true }
|
|
70
|
+
}
|
|
71
|
+
return $false
|
|
72
|
+
} catch {
|
|
73
|
+
return $true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
$sb = [System.Text.StringBuilder]::new()
|
|
78
|
+
[void]$sb.AppendLine("# Zoo Flow Bundle")
|
|
79
|
+
[void]$sb.AppendLine("")
|
|
80
|
+
[void]$sb.AppendLine("Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm') from commit $commit on branch $branch.")
|
|
81
|
+
[void]$sb.AppendLine("")
|
|
82
|
+
[void]$sb.AppendLine("This is a flat snapshot of every tracked file in the repo. Each file is shown under its repo-relative path.")
|
|
83
|
+
[void]$sb.AppendLine("")
|
|
84
|
+
[void]$sb.AppendLine("---")
|
|
85
|
+
[void]$sb.AppendLine("")
|
|
86
|
+
|
|
87
|
+
$included = 0
|
|
88
|
+
$skipped = 0
|
|
89
|
+
|
|
90
|
+
foreach ($rel in $files) {
|
|
91
|
+
$abs = Join-Path $repoRoot $rel
|
|
92
|
+
if (-not (Test-Path $abs -PathType Leaf)) { continue }
|
|
93
|
+
|
|
94
|
+
$size = (Get-Item $abs).Length
|
|
95
|
+
$ext = [IO.Path]::GetExtension($rel)
|
|
96
|
+
if (-not $ext) { $ext = [IO.Path]::GetFileName($rel) }
|
|
97
|
+
|
|
98
|
+
$lang = $extLang[$ext.ToLower()]
|
|
99
|
+
if (-not $lang) { $lang = "text" }
|
|
100
|
+
|
|
101
|
+
[void]$sb.AppendLine("## ``$rel``")
|
|
102
|
+
[void]$sb.AppendLine("")
|
|
103
|
+
|
|
104
|
+
if ($size -gt $MaxBytes) {
|
|
105
|
+
[void]$sb.AppendLine("_Skipped: $size bytes (exceeds MaxBytes)._")
|
|
106
|
+
[void]$sb.AppendLine("")
|
|
107
|
+
$skipped++
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (Test-IsBinary -Path $abs) {
|
|
112
|
+
[void]$sb.AppendLine("_Skipped: binary file ($size bytes)._")
|
|
113
|
+
[void]$sb.AppendLine("")
|
|
114
|
+
$skipped++
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
$content = [IO.File]::ReadAllText($abs)
|
|
119
|
+
$fence = '```'
|
|
120
|
+
if ($content -match '```') { $fence = '````' }
|
|
121
|
+
|
|
122
|
+
[void]$sb.AppendLine("$fence$lang")
|
|
123
|
+
[void]$sb.AppendLine($content.TrimEnd())
|
|
124
|
+
[void]$sb.AppendLine("$fence")
|
|
125
|
+
[void]$sb.AppendLine("")
|
|
126
|
+
|
|
127
|
+
$included++
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[IO.File]::WriteAllText($OutFile, $sb.ToString())
|
|
131
|
+
|
|
132
|
+
$bundleSize = (Get-Item $OutFile).Length
|
|
133
|
+
Write-Host "wrote: $OutFile"
|
|
134
|
+
Write-Host "size: $bundleSize bytes"
|
|
135
|
+
Write-Host "files: $($files.Count) total, $included included, $skipped skipped"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
10
|
+
const transcriptDir = path.join(packageRoot, "tests", "golden-transcripts");
|
|
11
|
+
|
|
12
|
+
const failures = [];
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(transcriptDir)) {
|
|
15
|
+
console.log("Golden transcript check skipped: no tests/golden-transcripts directory");
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const entries = fs.readdirSync(transcriptDir);
|
|
20
|
+
|
|
21
|
+
if (entries.length === 0) {
|
|
22
|
+
console.log("Golden transcript check skipped: empty tests/golden-transcripts directory");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const fullPath = path.join(transcriptDir, entry);
|
|
28
|
+
if (fs.statSync(fullPath).isDirectory()) continue;
|
|
29
|
+
if (!entry.endsWith(".md") && !entry.endsWith(".jsonl")) continue;
|
|
30
|
+
|
|
31
|
+
const text = fs.readFileSync(fullPath, "utf8");
|
|
32
|
+
|
|
33
|
+
// Structural checks
|
|
34
|
+
if (!text.trim()) {
|
|
35
|
+
failures.push(`${entry}: empty transcript`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (entry.endsWith(".md")) {
|
|
40
|
+
if (!/^#\s/m.test(text)) {
|
|
41
|
+
failures.push(`${entry}: must have at least one heading`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (entry.endsWith(".jsonl")) {
|
|
46
|
+
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
47
|
+
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(lines[idx]);
|
|
50
|
+
if (typeof parsed.name !== "string" || typeof parsed.user !== "string") {
|
|
51
|
+
failures.push(`${entry} line ${idx + 1}: missing name/user string fields`);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
failures.push(`${entry} line ${idx + 1}: invalid JSON — ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (failures.length > 0) {
|
|
61
|
+
console.error("\nGolden transcript validation failed:\n");
|
|
62
|
+
for (const failure of failures) {
|
|
63
|
+
console.error(`- ${failure}`);
|
|
64
|
+
}
|
|
65
|
+
console.error("");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`Golden transcript check passed: ${entries.length} files`);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
10
|
+
const readmePath = path.join(packageRoot, "README.md");
|
|
11
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
12
|
+
|
|
13
|
+
const failures = [];
|
|
14
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
15
|
+
const files = Array.isArray(packageJson.files) ? packageJson.files : [];
|
|
16
|
+
const forbidden = [".git", "node_modules", ".scratch", ".zoo-flow-backup", "*.tgz"];
|
|
17
|
+
|
|
18
|
+
for (const entry of files) {
|
|
19
|
+
if (forbidden.some((pattern) => pattern === entry || (pattern === "*.tgz" && entry.endsWith(".tgz")))) {
|
|
20
|
+
failures.push(`package.json files must not include ${entry}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isExternalLink(target) {
|
|
25
|
+
return /^(https?:|mailto:|#)/i.test(target);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isIncludedInPackage(targetPath) {
|
|
29
|
+
const normalized = targetPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
30
|
+
const top = normalized.split("/")[0];
|
|
31
|
+
return files.includes(normalized) || files.includes(top);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const readme = fs.readFileSync(readmePath, "utf8");
|
|
35
|
+
const linkRegex = /!??\[[^\]]*\]\(([^)]+)\)/g;
|
|
36
|
+
let match;
|
|
37
|
+
|
|
38
|
+
while ((match = linkRegex.exec(readme)) !== null) {
|
|
39
|
+
const rawTarget = match[1].trim();
|
|
40
|
+
const target = rawTarget.split(/\s+/)[0].replace(/^<|>$/g, "");
|
|
41
|
+
if (!target || isExternalLink(target)) continue;
|
|
42
|
+
|
|
43
|
+
const withoutAnchor = target.split("#")[0];
|
|
44
|
+
if (!withoutAnchor) continue;
|
|
45
|
+
|
|
46
|
+
const absolute = path.resolve(packageRoot, withoutAnchor);
|
|
47
|
+
if (!absolute.startsWith(packageRoot)) {
|
|
48
|
+
failures.push(`README link escapes package root: ${target}`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(absolute)) {
|
|
53
|
+
failures.push(`README link target does not exist: ${target}`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const relative = path.relative(packageRoot, absolute);
|
|
58
|
+
if (!isIncludedInPackage(relative)) {
|
|
59
|
+
failures.push(`README link target is excluded from package files: ${target}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (failures.length > 0) {
|
|
64
|
+
console.error("\nPackage link validation failed:\n");
|
|
65
|
+
for (const failure of failures) {
|
|
66
|
+
console.error(`- ${failure}`);
|
|
67
|
+
}
|
|
68
|
+
console.error("");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log("Package link validation passed: README local links are packaged");
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
11
|
+
|
|
12
|
+
const failures = [];
|
|
13
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
14
|
+
|
|
15
|
+
const expectedFiles = Array.isArray(pkg.files) ? pkg.files : [];
|
|
16
|
+
const requiredEntries = [
|
|
17
|
+
"bin/zoo-flow.js",
|
|
18
|
+
"package.json",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Run npm pack --dry-run and capture output
|
|
24
|
+
const result = spawnSync("npm", ["pack", "--dry-run"], {
|
|
25
|
+
cwd: packageRoot,
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
shell: true
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
let packOutput = "";
|
|
31
|
+
if (result.status !== 0) {
|
|
32
|
+
failures.push(`npm pack --dry-run failed: ${result.stderr}`);
|
|
33
|
+
} else {
|
|
34
|
+
packOutput = `${result.stdout}\n${result.stderr}`;
|
|
35
|
+
|
|
36
|
+
// Check that required entries appear in the tarball listing
|
|
37
|
+
for (const entry of requiredEntries) {
|
|
38
|
+
if (!packOutput.includes(entry)) {
|
|
39
|
+
failures.push(`Required entry missing from npm pack: ${entry}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check that all declared files entries produce content
|
|
44
|
+
for (const entry of expectedFiles) {
|
|
45
|
+
const absPath = path.join(packageRoot, entry);
|
|
46
|
+
if (!fs.existsSync(absPath)) {
|
|
47
|
+
failures.push(`Declared file entry does not exist: ${entry}`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const stat = fs.statSync(absPath);
|
|
51
|
+
if (stat.isDirectory()) {
|
|
52
|
+
// Check dir is non-empty
|
|
53
|
+
const contents = fs.readdirSync(absPath);
|
|
54
|
+
if (contents.length === 0) {
|
|
55
|
+
failures.push(`Declared file directory is empty: ${entry}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (failures.length > 0) {
|
|
62
|
+
console.error("\nPackage manifest integrity check failed:\n");
|
|
63
|
+
for (const failure of failures) {
|
|
64
|
+
console.error(`- ${failure}`);
|
|
65
|
+
}
|
|
66
|
+
console.error("");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log("Package manifest integrity check passed");
|