@minhduydev/mdpi 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.pi/VERSION +1 -1
  3. package/dist/template/.pi/extensions/templates-injector.ts +34 -6
  4. package/dist/template/.pi/prompts/INDEX.md +3 -9
  5. package/dist/template/.pi/skills/INDEX.md +81 -19
  6. package/dist/template/.pi/skills/accessibility-audit/SKILL.md +8 -2
  7. package/dist/template/.pi/skills/baseline-ui/SKILL.md +211 -0
  8. package/dist/template/.pi/skills/dcp-hygiene/SKILL.md +1 -1
  9. package/dist/template/.pi/skills/design-taste-frontend/SKILL.md +53 -42
  10. package/dist/template/.pi/skills/fixing-accessibility/SKILL.md +509 -0
  11. package/dist/template/.pi/skills/frontend-design/SKILL.md +60 -47
  12. package/dist/template/.pi/skills/frontend-design/references/animation/motion-advanced.md +88 -15
  13. package/dist/template/.pi/skills/frontend-design/references/animation/motion-core.md +148 -13
  14. package/dist/template/.pi/skills/frontend-design/references/shadcn/setup.md +127 -20
  15. package/dist/template/.pi/skills/frontend-ui-engineering/SKILL.md +21 -27
  16. package/dist/template/.pi/skills/nextjs-app-router/SKILL.md +334 -0
  17. package/dist/template/.pi/skills/nextjs-cache/SKILL.md +262 -0
  18. package/dist/template/.pi/skills/oklch-color-workflow/SKILL.md +426 -0
  19. package/dist/template/.pi/skills/production-hardening/SKILL.md +652 -0
  20. package/dist/template/.pi/skills/react-best-practices/SKILL.md +79 -1
  21. package/dist/template/.pi/skills/react-compiler/SKILL.md +237 -0
  22. package/dist/template/.pi/skills/react-hook-form/SKILL.md +374 -0
  23. package/dist/template/.pi/skills/react-server-actions/SKILL.md +299 -0
  24. package/dist/template/.pi/skills/shadcn-ui/SKILL.md +404 -0
  25. package/dist/template/.pi/skills/tanstack-query/SKILL.md +330 -0
  26. package/dist/template/.pi/skills/ui-craft-principles/SKILL.md +564 -0
  27. package/dist/template/.pi/skills/ui-quality-audit/SKILL.md +329 -0
  28. package/dist/template/.pi/skills/v0/SKILL.md +264 -0
  29. package/dist/template/.pi/skills/zustand/SKILL.md +333 -0
  30. package/dist/template/.pi/templates/DESIGN.md +76 -0
  31. package/dist/template/.pi/workflows/INDEX.md +2 -1
  32. package/dist/template/.pi/workflows/frontend-feature-workflow.md +343 -0
  33. package/dist/template/.pi/workflows/quality-loop.md +1 -1
  34. package/package.json +1 -1
  35. package/dist/template/.pi/prompts/loop-check.md +0 -87
  36. package/dist/template/.pi/prompts/loop-init.md +0 -157
  37. package/dist/template/.pi/prompts/loop-review.md +0 -90
  38. package/dist/template/.pi/skills/loop-audit/SKILL.md +0 -141
  39. package/dist/template/.pi/skills/loop-cost/SKILL.md +0 -130
  40. package/dist/template/.pi/skills/loop-engineering/SKILL.md +0 -175
  41. package/dist/template/.pi/templates/loop-github-action.yml +0 -162
  42. package/dist/template/.pi/templates/loop-orchestrator.sh +0 -514
  43. package/dist/template/.pi/templates/loop-orchestrator.test.ts +0 -332
  44. package/dist/template/.pi/templates/loop-orchestrator.ts +0 -936
  45. package/dist/template/.pi/templates/loop-state.json +0 -24
  46. package/dist/template/.pi/templates/loop-state.md +0 -98
  47. package/dist/template/.pi/templates/loop-vision.md +0 -110
  48. /package/dist/template/.pi/templates/{design.md → feature-design.md} +0 -0
@@ -1,332 +0,0 @@
1
- /**
2
- * loop-orchestrator.test.ts — pure-helper TDD for the Node SDK orchestrator (T9).
3
- *
4
- * Covers the pure, side-effect-free helpers only. The runtime smoke (real
5
- * worktree + real pi + real gh on a throwaway repo) is deferred to T15
6
- * (supervised checkpoint). Run with: `npx tsx --test .pi/templates/loop-orchestrator.test.ts`.
7
- */
8
-
9
- import { test } from "node:test";
10
- import assert from "node:assert/strict";
11
-
12
- import {
13
- parseGateCommand,
14
- buildMakerPrompt,
15
- nextItemId,
16
- updateStateJson,
17
- isAlreadyProcessed,
18
- auditShipToolCalls,
19
- enforceBudgetCap,
20
- accumulateUsage,
21
- MAKER_TOOLS,
22
- } from "./loop-orchestrator.ts";
23
-
24
- // ---------------------------------------------------------------------------
25
- // Sample fixtures
26
- // ---------------------------------------------------------------------------
27
-
28
- const VISION_WITH_GATE = `# Loop Vision
29
-
30
- ## Goal
31
- Do the thing.
32
-
33
- ## Scope
34
- - edit src/
35
-
36
- ## Gate
37
-
38
- Command (exit 0 = pass):
39
-
40
- \`\`\`bash
41
- npm test
42
- \`\`\`
43
-
44
- **Pass:** ship.
45
- `;
46
-
47
- const VISION_NO_GATE_HEADING = `# Loop Vision
48
-
49
- ## Goal
50
- Do the thing.
51
-
52
- \`\`\`bash
53
- npm test
54
- \`\`\`
55
- `;
56
-
57
- const VISION_GATE_EMPTY_BLOCK = `# Loop Vision
58
-
59
- ## Gate
60
-
61
- \`\`\`bash
62
- \`\`\`
63
-
64
- end
65
- `;
66
-
67
- const VISION_GATE_MULTIPLE_BLOCKS = `# Loop Vision
68
-
69
- ## Gate
70
-
71
- \`\`\`bash
72
- npm test
73
- \`\`\`
74
-
75
- \`\`\`bash
76
- npm run lint
77
- \`\`\`
78
-
79
- end
80
- `;
81
-
82
- const VISION_GATE_UNTERMINATED = `# Loop Vision
83
-
84
- ## Gate
85
-
86
- \`\`\`bash
87
- npm test
88
- `;
89
-
90
- const VISION_GATE_NEXT_HEADING_ENDS = `# Loop Vision
91
-
92
- ## Gate
93
-
94
- \`\`\`bash
95
- npm test
96
- \`\`\`
97
-
98
- ## Scope
99
- - edit src/
100
- `;
101
-
102
- const STATE = {
103
- loop_name: "ci-triage",
104
- owner: "ops",
105
- cadence: "manual",
106
- last_run: null,
107
- in_progress: ["item-9"],
108
- completed: [],
109
- escalated: [],
110
- failures: [],
111
- lessons: [],
112
- processed: ["item-1", "item-2"],
113
- stop_conditions_met: [],
114
- metrics: {
115
- runs: 0,
116
- killed: false,
117
- kill_reason: null,
118
- tokens_used: 0,
119
- token_cap: null,
120
- pr_opened: 0,
121
- items_fixed: 0,
122
- items_skipped: 0,
123
- items_escalated: 0,
124
- },
125
- };
126
-
127
- // ---------------------------------------------------------------------------
128
- // parseGateCommand
129
- // ---------------------------------------------------------------------------
130
-
131
- test("parseGateCommand: extracts first ```bash under ## Gate", () => {
132
- assert.equal(parseGateCommand(VISION_WITH_GATE), "npm test");
133
- });
134
-
135
- test("parseGateCommand: trims surrounding whitespace per line", () => {
136
- const md = `## Gate\n\n\`\`\`bash\n npm test \n npm run lint \n\`\`\`\n`;
137
- assert.equal(parseGateCommand(md), "npm test\nnpm run lint");
138
- });
139
-
140
- test("parseGateCommand: section ends at next level-2 heading (still returns the one block)", () => {
141
- assert.equal(parseGateCommand(VISION_GATE_NEXT_HEADING_ENDS), "npm test");
142
- });
143
-
144
- test("parseGateCommand: returns null when ## Gate heading is missing", () => {
145
- assert.equal(parseGateCommand(VISION_NO_GATE_HEADING), null);
146
- });
147
-
148
- test("parseGateCommand: returns null when the bash block is empty", () => {
149
- assert.equal(parseGateCommand(VISION_GATE_EMPTY_BLOCK), null);
150
- });
151
-
152
- test("parseGateCommand: returns null when multiple bash blocks under ## Gate", () => {
153
- assert.equal(parseGateCommand(VISION_GATE_MULTIPLE_BLOCKS), null);
154
- });
155
-
156
- test("parseGateCommand: returns null when the block is unterminated", () => {
157
- assert.equal(parseGateCommand(VISION_GATE_UNTERMINATED), null);
158
- });
159
-
160
- test("parseGateCommand: returns null for empty input", () => {
161
- assert.equal(parseGateCommand(""), null);
162
- });
163
-
164
- // ---------------------------------------------------------------------------
165
- // buildMakerPrompt
166
- // ---------------------------------------------------------------------------
167
-
168
- test("buildMakerPrompt: contains the loop name", () => {
169
- const p = buildMakerPrompt("ci-triage", VISION_WITH_GATE, STATE);
170
- assert.match(p, /ci-triage/);
171
- });
172
-
173
- test("buildMakerPrompt: tells the maker it cannot ship", () => {
174
- const p = buildMakerPrompt("ci-triage", VISION_WITH_GATE, STATE);
175
- assert.match(p, /cannot ship|CANNOT SHIP|do not.*push/i);
176
- });
177
-
178
- test("buildMakerPrompt: references VISION.md", () => {
179
- const p = buildMakerPrompt("ci-triage", VISION_WITH_GATE, STATE);
180
- assert.match(p, /VISION\.md/);
181
- });
182
-
183
- // ---------------------------------------------------------------------------
184
- // nextItemId
185
- // ---------------------------------------------------------------------------
186
-
187
- test("nextItemId: returns the first in_progress item when present", () => {
188
- assert.equal(nextItemId(STATE), "item-9");
189
- });
190
-
191
- test("nextItemId: falls back to provided fallback when in_progress is empty", () => {
192
- const s = { ...STATE, in_progress: [] };
193
- assert.equal(nextItemId(s, "manual-42"), "manual-42");
194
- });
195
-
196
- // ---------------------------------------------------------------------------
197
- // updateStateJson
198
- // ---------------------------------------------------------------------------
199
-
200
- test("updateStateJson: applies a shallow top-level patch", () => {
201
- const next = updateStateJson(STATE, { last_run: "2026-01-01T00:00:00Z" });
202
- assert.equal(next.last_run, "2026-01-01T00:00:00Z");
203
- // immutability: original untouched
204
- assert.equal(STATE.last_run, null);
205
- });
206
-
207
- test("updateStateJson: shallow-merges metrics", () => {
208
- const next = updateStateJson(STATE, { metrics: { ...STATE.metrics, runs: 5 } });
209
- assert.equal(next.metrics.runs, 5);
210
- assert.equal(next.metrics.items_fixed, 0);
211
- });
212
-
213
- // ---------------------------------------------------------------------------
214
- // isAlreadyProcessed
215
- // ---------------------------------------------------------------------------
216
-
217
- test("isAlreadyProcessed: true for a processed item", () => {
218
- assert.equal(isAlreadyProcessed(STATE, "item-1"), true);
219
- });
220
-
221
- test("isAlreadyProcessed: false for an unprocessed item", () => {
222
- assert.equal(isAlreadyProcessed(STATE, "item-99"), false);
223
- });
224
-
225
- test("isAlreadyProcessed: false when processed is missing", () => {
226
- const s = { ...STATE, processed: undefined as unknown as string[] };
227
- assert.equal(isAlreadyProcessed(s, "item-1"), false);
228
- });
229
-
230
- // ---------------------------------------------------------------------------
231
- // auditShipToolCalls
232
- // ---------------------------------------------------------------------------
233
-
234
- test("auditShipToolCalls: ok for maker-only tools", () => {
235
- assert.deepEqual(auditShipToolCalls(["bash", "edit", "read", "grep", "find", "write"]), {
236
- ok: true,
237
- offenders: [],
238
- });
239
- });
240
-
241
- test("auditShipToolCalls: flags push", () => {
242
- assert.deepEqual(auditShipToolCalls(["bash", "push"]), {
243
- ok: false,
244
- offenders: ["push"],
245
- });
246
- });
247
-
248
- test("auditShipToolCalls: flags pr and slack", () => {
249
- const r = auditShipToolCalls(["bash", "pr", "slack"]);
250
- assert.equal(r.ok, false);
251
- assert.deepEqual(r.offenders.sort(), ["pr", "slack"]);
252
- });
253
-
254
- test("auditShipToolCalls: case-insensitive", () => {
255
- assert.deepEqual(auditShipToolCalls(["Push", "SLACK"]), {
256
- ok: false,
257
- offenders: ["Push", "SLACK"],
258
- });
259
- });
260
-
261
- test("MAKER_TOOLS: does not include any ship tool", () => {
262
- const ship = new Set(["push", "pr", "slack"]);
263
- for (const t of MAKER_TOOLS) {
264
- assert.equal(ship.has(t), false, `maker allowlist must not include ${t}`);
265
- }
266
- });
267
-
268
- // ---------------------------------------------------------------------------
269
- // enforceBudgetCap (FR13 — budget cap enforcement)
270
- // ---------------------------------------------------------------------------
271
-
272
- test("enforceBudgetCap: null cap never kills", () => {
273
- assert.deepEqual(enforceBudgetCap({ tokens: { total: 999999 } }, null), { kill: false, reason: null });
274
- });
275
-
276
- test("enforceBudgetCap: under cap does not kill", () => {
277
- assert.deepEqual(enforceBudgetCap({ tokens: { total: 100 } }, 1000), { kill: false, reason: null });
278
- });
279
-
280
- test("enforceBudgetCap: over cap kills with budget_cap_exceeded reason", () => {
281
- assert.deepEqual(enforceBudgetCap({ tokens: { total: 1500 } }, 1000), { kill: true, reason: "budget_cap_exceeded" });
282
- });
283
-
284
- test("enforceBudgetCap: missing tokens treated as 0 (no kill under cap)", () => {
285
- assert.deepEqual(enforceBudgetCap({}, 1000), { kill: false, reason: null });
286
- });
287
-
288
- test("enforceBudgetCap: exactly at cap does not kill (strict >)", () => {
289
- assert.deepEqual(enforceBudgetCap({ tokens: { total: 1000 } }, 1000), { kill: false, reason: null });
290
- });
291
-
292
- // ---------------------------------------------------------------------------
293
- // accumulateUsage (FR13 — budget cap token accumulation)
294
- // ---------------------------------------------------------------------------
295
- // Verifies the message_end listener's `+=` accumulation: each assistant
296
- // message_end event contributes its per-turn Usage.totalTokens delta, and the
297
- // cumulative total must equal the sum of all per-turn deltas. (User
298
- // message_end events carry no .usage and must NOT contribute.)
299
-
300
- test("accumulateUsage: sums totalTokens across two assistant message_end events", () => {
301
- const events = [
302
- { type: "message_end", message: { role: "assistant", usage: { totalTokens: 1200 } } },
303
- { type: "message_end", message: { role: "assistant", usage: { totalTokens: 800 } } },
304
- ];
305
- assert.equal(accumulateUsage(0, events), 2000);
306
- });
307
-
308
- test("accumulateUsage: accumulates onto a non-zero starting total", () => {
309
- const events = [
310
- { type: "message_end", message: { role: "assistant", usage: { totalTokens: 300 } } },
311
- { type: "message_end", message: { role: "assistant", usage: { totalTokens: 500 } } },
312
- ];
313
- assert.equal(accumulateUsage(250, events), 1050);
314
- });
315
-
316
- test("accumulateUsage: ignores user message_end events (no .usage)", () => {
317
- const events = [
318
- { type: "message_end", message: { role: "user" } },
319
- { type: "message_end", message: { role: "assistant", usage: { totalTokens: 750 } } },
320
- { type: "message_end", message: { role: "user", usage: {} } },
321
- ];
322
- assert.equal(accumulateUsage(0, events), 750);
323
- });
324
-
325
- test("accumulateUsage: falls back to total / input+output when totalTokens missing", () => {
326
- const events = [
327
- { type: "message_end", message: { role: "assistant", usage: { total: 410 } } },
328
- { type: "message_end", message: { role: "assistant", usage: { input: 100, output: 200 } } },
329
- { type: "message_end", message: { role: "assistant", usage: { totalTokens: 90 } } },
330
- ];
331
- assert.equal(accumulateUsage(0, events), 800);
332
- });