@pixel-point/toolcraft 0.0.12 → 0.0.14

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 (37) hide show
  1. package/README.md +4 -2
  2. package/package.json +1 -1
  3. package/src/cli.mjs +18 -45
  4. package/src/cli.test.mjs +141 -6
  5. package/src/command-runner.mjs +71 -0
  6. package/src/dependency-install.mjs +87 -0
  7. package/src/generate.mjs +32 -4
  8. package/src/generate.test.mjs +116 -15
  9. package/src/package-json.mjs +11 -2
  10. package/src/package-json.test.mjs +27 -0
  11. package/src/package-manager.mjs +123 -0
  12. package/src/package-manager.test.mjs +80 -0
  13. package/templates/runtime/contracts/component-contracts.test.ts +1 -1
  14. package/templates/runtime/contracts/component-contracts.ts +1 -1
  15. package/templates/starter/AGENTS.md +4 -3
  16. package/templates/starter/docs/toolcraft/README.md +15 -0
  17. package/templates/starter/docs/toolcraft/acceptance-testing.md +2 -0
  18. package/templates/starter/docs/toolcraft/assembly-workflow.md +35 -171
  19. package/templates/starter/docs/toolcraft/component-rules.md +12 -188
  20. package/templates/starter/docs/toolcraft/core/control-selection.md +93 -0
  21. package/templates/starter/docs/toolcraft/core/layout.md +104 -0
  22. package/templates/starter/docs/toolcraft/core/media-upload.md +85 -0
  23. package/templates/starter/docs/toolcraft/core/performance.md +83 -0
  24. package/templates/starter/docs/toolcraft/core/reference-study.md +115 -0
  25. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +53 -0
  26. package/templates/starter/docs/toolcraft/core/setup-export.md +86 -0
  27. package/templates/starter/docs/toolcraft/core/timeline-animation.md +67 -0
  28. package/templates/starter/docs/toolcraft/custom-controls.md +2 -0
  29. package/templates/starter/docs/toolcraft/performance.md +2 -0
  30. package/templates/starter/docs/toolcraft/renderer-technique.md +2 -0
  31. package/templates/starter/docs/toolcraft/schema-reference.md +117 -367
  32. package/templates/starter/docs/toolcraft/workflow.md +12 -10
  33. package/templates/starter/package.json +1 -0
  34. package/templates/starter/scripts/check-toolcraft-docs.mjs +28 -6
  35. package/templates/starter/scripts/run-vite-on-free-port.mjs +25 -6
  36. package/templates/starter/src/app/starter-acceptance.test.ts +24 -15
  37. package/templates/starter/src/app/starter-performance.test.ts +17 -7
@@ -17,22 +17,24 @@ Before planning or editing app code, runtime code, controls, canvas, panels, ren
17
17
 
18
18
  Do not edit implementation files until this preflight is complete.
19
19
 
20
+ Core modules are required reading when listed by the routing table. Read each listed module fully. Do not rely on a truncated terminal excerpt; reopen the specific module or use a narrower range when a tool output is clipped.
21
+
20
22
  ## Task Routing
21
23
 
22
24
  Use the smallest reading set that covers the changed surface. If a task touches multiple surfaces, combine every matching row instead of picking only the closest one.
23
25
 
24
26
  | Task type | Read before editing |
25
27
  | --- | --- |
26
- | App assembly, route structure, generated app porting | `assembly-workflow.md`, `decision-contract.md` |
27
- | Reference app study, audit, or port | `assembly-workflow.md`, `schema-reference.md`, `acceptance-testing.md`, `decision-contract.md` |
28
- | Schema, controls, defaults, persistence, actions | `schema-reference.md`, `component-rules.md`, `acceptance-testing.md` |
29
- | Custom controls | `custom-controls.md`, `component-rules.md`, `acceptance-testing.md` |
30
- | Renderer, canvas output, visual technique | `renderer-technique.md`, `performance.md`, `acceptance-testing.md` |
31
- | Timeline, keyframes, animation transport | `decision-contract.md`, `component-rules.md`, `acceptance-testing.md`, `performance.md` |
32
- | Layers | `decision-contract.md`, `component-rules.md`, `acceptance-testing.md` |
33
- | Export, copy, media, background | `schema-reference.md`, `component-rules.md`, `acceptance-testing.md`, `performance.md` |
34
- | Broken control, visual mismatch, failed build, export bug, performance issue | `decision-contract.md`, the relevant component/runtime doc, and the failing test/log first |
35
- | Figma implementation | Use Figma MCP/design context, then `assembly-workflow.md` and the relevant component docs |
28
+ | App assembly, route structure, generated app porting | `core/runtime-boundary.md`, `assembly-workflow.md`, `decision-contract.md` |
29
+ | Reference app study, audit, or port | `core/reference-study.md`, `core/runtime-boundary.md`, `assembly-workflow.md`, `schema-reference.md`, `acceptance-testing.md`, `decision-contract.md` |
30
+ | Schema, controls, defaults, persistence, actions | `core/control-selection.md`, `core/layout.md`, `schema-reference.md`, `component-rules.md`, `acceptance-testing.md` |
31
+ | Custom controls | `core/control-selection.md`, `core/layout.md`, `custom-controls.md`, `component-rules.md`, `acceptance-testing.md` |
32
+ | Renderer, canvas output, visual technique | `core/runtime-boundary.md`, `core/performance.md`, `renderer-technique.md`, `performance.md`, `acceptance-testing.md` |
33
+ | Timeline, keyframes, animation transport | `core/timeline-animation.md`, `core/performance.md`, `decision-contract.md`, `component-rules.md`, `acceptance-testing.md` |
34
+ | Layers | `core/runtime-boundary.md`, `core/layout.md`, `decision-contract.md`, `component-rules.md`, `acceptance-testing.md` |
35
+ | Export, copy, media, background | `core/setup-export.md`, `core/media-upload.md`, `schema-reference.md`, `component-rules.md`, `acceptance-testing.md`, `performance.md` |
36
+ | Broken control, visual mismatch, failed build, export bug, performance issue | `decision-contract.md`, the relevant `core/*` module, the relevant component/runtime doc, and the failing test/log first |
37
+ | Figma implementation | Use Figma MCP/design context, then `core/reference-study.md`, `core/runtime-boundary.md`, `assembly-workflow.md`, and the relevant component docs |
36
38
 
37
39
  ## Worklog Gate
38
40
 
@@ -34,6 +34,7 @@
34
34
  "class-variance-authority": "^0.7.1",
35
35
  "clsx": "^2.1.1",
36
36
  "cmdk": "^1.1.1",
37
+ "cross-spawn": "^7.0.6",
37
38
  "lucide-react": "^0.511.0",
38
39
  "motion": "^11.16.3",
39
40
  "react": "^19.2.0",
@@ -19,6 +19,17 @@ const requiredDocs = [
19
19
  "component-rules.md",
20
20
  ];
21
21
 
22
+ const requiredCoreDocs = [
23
+ "core/runtime-boundary.md",
24
+ "core/setup-export.md",
25
+ "core/control-selection.md",
26
+ "core/layout.md",
27
+ "core/media-upload.md",
28
+ "core/timeline-animation.md",
29
+ "core/performance.md",
30
+ "core/reference-study.md",
31
+ ];
32
+
22
33
  const requiredProjectFiles = ["LICENSE.md", "NOTICE.md"];
23
34
 
24
35
  const requiredRuleIds = [
@@ -43,17 +54,21 @@ const requiredRuleIds = [
43
54
  "workflow-required",
44
55
  ];
45
56
 
46
- const requiredAgentsLinks = [
57
+ const requiredAgentsEntryLinks = [
47
58
  "workflow.md",
59
+ "core/",
60
+ ];
61
+
62
+ const requiredWorkflowLinks = [
63
+ ...requiredCoreDocs,
48
64
  "assembly-workflow.md",
49
65
  "decision-contract.md",
50
66
  "schema-reference.md",
67
+ "component-rules.md",
51
68
  "acceptance-testing.md",
52
69
  "performance.md",
53
70
  "renderer-technique.md",
54
- "agent-worklog.md",
55
71
  "custom-controls.md",
56
- "component-rules.md",
57
72
  ];
58
73
 
59
74
  const requiredWorkflowTerms = [
@@ -90,7 +105,7 @@ async function fileExists(relativePath) {
90
105
 
91
106
  const failures = [];
92
107
 
93
- for (const fileName of requiredDocs) {
108
+ for (const fileName of [...requiredDocs, ...requiredCoreDocs]) {
94
109
  const relativePath = `docs/toolcraft/${fileName}`;
95
110
 
96
111
  if (!(await fileExists(relativePath))) {
@@ -118,12 +133,13 @@ for (const ruleId of requiredRuleIds) {
118
133
  }
119
134
  }
120
135
 
121
- for (const fileName of requiredDocs) {
136
+ for (const fileName of [...requiredDocs, ...requiredCoreDocs]) {
122
137
  const source = await readText(`docs/toolcraft/${fileName}`);
123
138
  docsSources.push(source);
124
139
  }
125
140
 
126
141
  const combinedSource = docsSources.join("\n");
142
+ const workflowSource = await readText("docs/toolcraft/workflow.md");
127
143
 
128
144
  for (const pattern of forbiddenTextPatterns) {
129
145
  if (pattern.test(combinedSource)) {
@@ -131,12 +147,18 @@ for (const pattern of forbiddenTextPatterns) {
131
147
  }
132
148
  }
133
149
 
134
- for (const localDoc of requiredAgentsLinks) {
150
+ for (const localDoc of requiredAgentsEntryLinks) {
135
151
  if (!agentsSource.includes(`docs/toolcraft/${localDoc}`)) {
136
152
  failures.push(`AGENTS.md must link docs/toolcraft/${localDoc}`);
137
153
  }
138
154
  }
139
155
 
156
+ for (const localDoc of requiredWorkflowLinks) {
157
+ if (!workflowSource.includes(localDoc)) {
158
+ failures.push(`docs/toolcraft/workflow.md must route to ${localDoc}`);
159
+ }
160
+ }
161
+
140
162
  for (const term of requiredWorkflowTerms) {
141
163
  if (!agentsSource.includes(term)) {
142
164
  failures.push(`AGENTS.md must mention "${term}"`);
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { spawn } from "node:child_process";
3
+ import { access } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ import spawn from "cross-spawn";
4
8
 
5
9
  import {
6
10
  findAvailablePort,
@@ -14,6 +18,8 @@ import {
14
18
  } from "./toolcraft-port.mjs";
15
19
 
16
20
  const viteCommand = process.argv[2] ?? "dev";
21
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
22
+ const projectDir = join(scriptDir, "..");
17
23
  const restartArg = "--toolcraft-restart";
18
24
  const rawPassthroughArgs = process.argv.slice(3).filter((arg) => arg !== "--");
19
25
  const restartSamePort =
@@ -49,7 +55,7 @@ if (savedPort) {
49
55
  process.exit(0);
50
56
  } catch (error) {
51
57
  console.error(
52
- `Saved Toolcraft port ${savedPort} is busy, but it is not serving this app. Use pnpm dev:restart or pnpm preview:restart to reclaim the saved port, or remove .toolcraft/server-port.json to assign a new port. ${error?.message ?? ""}`.trim(),
58
+ `Saved Toolcraft port ${savedPort} is busy, but it is not serving this app. Use the restart script to reclaim the saved port, or remove .toolcraft/server-port.json to assign a new port. ${error?.message ?? ""}`.trim(),
53
59
  );
54
60
  process.exit(1);
55
61
  }
@@ -66,12 +72,25 @@ if (!port) {
66
72
  }
67
73
  }
68
74
 
69
- const pnpmCommand = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
75
+ const viteBinPath = join(
76
+ projectDir,
77
+ "node_modules",
78
+ ".bin",
79
+ process.platform === "win32" ? "vite.cmd" : "vite",
80
+ );
81
+
82
+ try {
83
+ await access(viteBinPath);
84
+ } catch {
85
+ console.error(
86
+ "Vite is not installed. Run this app's dependency installation command before starting the dev server.",
87
+ );
88
+ process.exit(1);
89
+ }
90
+
70
91
  const child = spawn(
71
- pnpmCommand,
92
+ viteBinPath,
72
93
  [
73
- "exec",
74
- "vite",
75
94
  viteCommand,
76
95
  "--host",
77
96
  "127.0.0.1",
@@ -554,8 +554,12 @@ function escapeRegExp(value: string): string {
554
554
 
555
555
  const incompleteRequiredCheckPattern =
556
556
  /\b(?:fail(?:ed|s|ing)?|not complete|not completed|incomplete|pending|blocked|planned|todo|to do|to run|will run|not run|not started|not yet|queued)\b/i;
557
+ const packageScriptPrefixPattern = String.raw`(?:pnpm\s+|npm\s+run\s+)?`;
557
558
  const requiredCheckNamePattern =
558
- /\b(?:(?:pnpm\s+)?(?:verify:final|verify:perf|test:browser:perf|test:browser|verify:quick|test|build)|browser performance checkpoint|agent-browser|playwright-fallback)\b/i;
559
+ new RegExp(
560
+ String.raw`\b(?:${packageScriptPrefixPattern}(?:verify:final|verify:perf|test:browser:perf|test:browser|verify:quick|test|build)|browser performance checkpoint|agent-browser|playwright-fallback)\b`,
561
+ "i",
562
+ );
559
563
  const browserPerformanceCheckpointPattern =
560
564
  /\b(?:agent-browser|browser performance checkpoint|performance checkpoint|verify:perf|test:browser:perf|playwright-fallback)\b/i;
561
565
  const fallbackPerfReasonPattern =
@@ -599,7 +603,7 @@ function isPassedAgentBrowserPerfRunLine(line: string): boolean {
599
603
  function isPassedPlaywrightFallbackPerfRunLine(line: string): boolean {
600
604
  return (
601
605
  isVerificationEvidenceLine(line) &&
602
- (/\bpnpm\s+verify:perf\b/i.test(line) ||
606
+ (/\b(?:pnpm\s+verify:perf|npm\s+run\s+verify:perf|verify:perf)\b/i.test(line) ||
603
607
  (/\bplaywright-fallback\b/i.test(line) &&
604
608
  /\b(?:performance|perf|checkpoint)\b/i.test(line))) &&
605
609
  !isIncompleteOrSkippedLine(line)
@@ -643,7 +647,7 @@ function getAgentWorklogVerificationGateErrors(source: string): string[] {
643
647
 
644
648
  if (!hasPassedPerfRun && !hasAllowedPerfSkip) {
645
649
  errors.push(
646
- "agent-worklog.md Verification must record the browser performance checkpoint for first working product delivery: use agent-browser when available, or `pnpm verify:perf` with an explicit fallback reason when no agent browser or CI/non-agent automation is used.",
650
+ "agent-worklog.md Verification must record the browser performance checkpoint for first working product delivery: use agent-browser when available, or this app's verify:perf script with an explicit fallback reason when no agent browser or CI/non-agent automation is used.",
647
651
  );
648
652
  }
649
653
 
@@ -721,7 +725,11 @@ function getAgentWorklogValidationErrors(source: string): string[] {
721
725
  );
722
726
  }
723
727
 
724
- if (!/\bpnpm\s+(verify|test|build|typecheck)|browser|Playwright|perf/i.test(verificationBody)) {
728
+ if (
729
+ !/\b(?:pnpm\s+(?:verify|test|build|typecheck)|npm\s+run\s+(?:verify|test|build|typecheck)|browser|Playwright|perf)\b/i.test(
730
+ verificationBody,
731
+ )
732
+ ) {
725
733
  errors.push("agent-worklog.md Verification must list concrete test/build/browser/performance checks.");
726
734
  }
727
735
 
@@ -3182,19 +3190,20 @@ describe("Toolcraft template app acceptance coverage", () => {
3182
3190
  });
3183
3191
 
3184
3192
  it("documents control selection inventory and custom built-in fit checks", () => {
3185
- const schemaReference = readToolcraftDoc("schema-reference.md");
3193
+ const coreControlSelection = readToolcraftDoc("core/control-selection.md");
3186
3194
  const componentRules = readToolcraftDoc("component-rules.md");
3187
3195
  const acceptanceTesting = readToolcraftDoc("acceptance-testing.md");
3188
3196
 
3189
- expect(schemaReference).toContain("Control Selection Inventory");
3190
- expect(schemaReference).toContain("Product need:");
3191
- expect(schemaReference).toContain("Candidate built-ins checked:");
3192
- expect(schemaReference).toContain("Rejected alternatives:");
3197
+ expect(coreControlSelection).toContain("Control Selection Inventory");
3198
+ expect(coreControlSelection).toContain("Product need:");
3199
+ expect(coreControlSelection).toContain("Candidate built-ins checked:");
3200
+ expect(coreControlSelection).toContain("Best built-in:");
3201
+ expect(coreControlSelection).toContain("Rejected alternatives:");
3202
+ expect(coreControlSelection).toContain("Target:");
3203
+ expect(coreControlSelection).toContain("Custom Control Gate");
3193
3204
 
3194
3205
  expect(componentRules).toContain("Control Decision Catalog");
3195
- expect(componentRules).toContain("Exact owner");
3196
- expect(componentRules).toContain("Best fit");
3197
- expect(componentRules).toContain("Custom escape hatch");
3206
+ expect(componentRules).toContain("core/control-selection.md");
3198
3207
 
3199
3208
  expect(acceptanceTesting).toContain("wrong-substitution");
3200
3209
  expect(acceptanceTesting).toContain("built-in fit check");
@@ -3689,7 +3698,7 @@ describe("Toolcraft template app acceptance coverage", () => {
3689
3698
  expect.arrayContaining([
3690
3699
  "agent-worklog.md required checks must be passed before final delivery; do not report failed, incomplete, pending, or blocked verification.",
3691
3700
  "agent-worklog.md must not list required checks as skipped unless they are explicitly not required for a post-first-working non-performance edit.",
3692
- "agent-worklog.md Verification must record the browser performance checkpoint for first working product delivery: use agent-browser when available, or `pnpm verify:perf` with an explicit fallback reason when no agent browser or CI/non-agent automation is used.",
3701
+ "agent-worklog.md Verification must record the browser performance checkpoint for first working product delivery: use agent-browser when available, or this app's verify:perf script with an explicit fallback reason when no agent browser or CI/non-agent automation is used.",
3693
3702
  ]),
3694
3703
  );
3695
3704
  });
@@ -3775,7 +3784,7 @@ describe("Toolcraft template app acceptance coverage", () => {
3775
3784
  `;
3776
3785
 
3777
3786
  expect(getAgentWorklogValidationErrors(missingPerfWorklog)).toContain(
3778
- "agent-worklog.md Verification must record the browser performance checkpoint for first working product delivery: use agent-browser when available, or `pnpm verify:perf` with an explicit fallback reason when no agent browser or CI/non-agent automation is used.",
3787
+ "agent-worklog.md Verification must record the browser performance checkpoint for first working product delivery: use agent-browser when available, or this app's verify:perf script with an explicit fallback reason when no agent browser or CI/non-agent automation is used.",
3779
3788
  );
3780
3789
  });
3781
3790
 
@@ -3992,7 +4001,7 @@ describe("Toolcraft template app acceptance coverage", () => {
3992
4001
  `;
3993
4002
 
3994
4003
  expect(getAgentWorklogValidationErrors(missingFallbackReasonWorklog)).toContain(
3995
- "agent-worklog.md Verification must record the browser performance checkpoint for first working product delivery: use agent-browser when available, or `pnpm verify:perf` with an explicit fallback reason when no agent browser or CI/non-agent automation is used.",
4004
+ "agent-worklog.md Verification must record the browser performance checkpoint for first working product delivery: use agent-browser when available, or this app's verify:perf script with an explicit fallback reason when no agent browser or CI/non-agent automation is used.",
3996
4005
  );
3997
4006
  });
3998
4007
 
@@ -321,6 +321,8 @@ describe("Toolcraft template app performance coverage", () => {
321
321
  const packageJson = JSON.parse(readFileSync(join(projectDir, "package.json"), "utf8")) as {
322
322
  scripts?: Record<string, string>;
323
323
  };
324
+ const runScriptPattern = (scriptName: string) =>
325
+ `(?:pnpm ${scriptName}|npm run ${scriptName})`;
324
326
 
325
327
  expect(
326
328
  packageJson.scripts?.["test:browser"],
@@ -332,14 +334,22 @@ describe("Toolcraft template app performance coverage", () => {
332
334
  ).toBe(
333
335
  'playwright install chromium && playwright test --grep "browser perf:" --workers=1 --pass-with-no-tests',
334
336
  );
335
- expect(packageJson.scripts?.["verify:quick"]).toBe("pnpm ai:check && pnpm test");
336
- expect(packageJson.scripts?.["verify:ui"]).toBe("pnpm test:browser");
337
- expect(packageJson.scripts?.["verify:perf"]).toBe("pnpm test:browser:perf");
338
- expect(packageJson.scripts?.["verify:perf:playwright"]).toBe(
339
- "pnpm test:browser:perf",
337
+ expect(packageJson.scripts?.["verify:quick"]).toMatch(
338
+ new RegExp(`^${runScriptPattern("ai:check")} && ${runScriptPattern("test")}$`),
340
339
  );
341
- expect(packageJson.scripts?.["verify:final"]).toBe(
342
- "pnpm ai:check && pnpm test && pnpm build && pnpm test:browser",
340
+ expect(packageJson.scripts?.["verify:ui"]).toMatch(
341
+ new RegExp(`^${runScriptPattern("test:browser")}$`),
342
+ );
343
+ expect(packageJson.scripts?.["verify:perf"]).toMatch(
344
+ new RegExp(`^${runScriptPattern("test:browser:perf")}$`),
345
+ );
346
+ expect(packageJson.scripts?.["verify:perf:playwright"]).toMatch(
347
+ new RegExp(`^${runScriptPattern("test:browser:perf")}$`),
348
+ );
349
+ expect(packageJson.scripts?.["verify:final"]).toMatch(
350
+ new RegExp(
351
+ `^${runScriptPattern("ai:check")} && ${runScriptPattern("test")} && ${runScriptPattern("build")} && ${runScriptPattern("test:browser")}$`,
352
+ ),
343
353
  );
344
354
  });
345
355