cabloy 5.1.201 → 5.1.203
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/.cabloy-version +1 -1
- package/CHANGELOG.md +17 -0
- package/CONTRIBUTING.md +18 -17
- package/README.md +2 -2
- package/package.json +2 -3
- package/repo-agent-governance/managed-assets.json +33 -33
- package/repo-agent-governance/policies/repository.md +1 -0
- package/repo-agent-governance/skills/cabloy-spec-execution/SKILL.md +3 -2
- package/repo-agent-governance/skills/cabloy-spec-execution/references/execution-protocol.md +1 -1
- package/repo-agent-governance/skills/cabloy-spec-execution/references/status-and-evidence.md +11 -10
- package/repo-agent-governance/skills/cabloy-spec-generation/SKILL.md +1 -1
- package/repo-agent-governance/skills/cabloy-spec-generation/references/canonical-spec-input.md +4 -2
- package/repo-agent-governance/skills/cabloy-spec-generation/references/repo-specs-document-set.md +16 -13
- package/repo-agent-governance/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +18 -11
- package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +2 -2
- package/repo-agent-governance/skills/cabloy-workflow/evals/evals.json +1 -1
- package/repo-agent-governance/skills/cabloy-worktree-environment/SKILL.md +5 -5
- package/repo-agent-governance/tests/spec-audit.test.mjs +323 -0
- package/repo-agent-governance/tools/spec-audit/audit.mjs +129 -3
- package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.mjs +26 -7
- package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.test.mjs +82 -0
- package/repo-agent-governance/tools/spec-charts/spec-parser.mjs +40 -1
- package/repo-docs/ai/ai-spec-driven-development.md +1 -0
- package/repo-docs/ai/playbook-spec-execution.md +3 -2
- package/repo-docs/ai/playbook-spec-generation.md +1 -1
- package/repo-docs/assets/img/cabloy-admin-ssr-cover-en.png +0 -0
- package/repo-docs/assets/img/cabloy-admin-tabs-cover-en.png +0 -0
- package/repo-docs/assets/img/cabloy-ai-crud-cover-en.png +0 -0
- package/repo-docs/demo.md +14 -0
- package/repo-docs/frontend/scripts.md +10 -18
- package/repo-docs/fullstack/parallel-worktree-environment.md +2 -2
- package/repo-docs/index.md +2 -2
- package/repo-docs/reference/repo-scripts.md +15 -31
- package/repo-e2e/config/playwright.config.ts +14 -19
- package/repo-e2e/scripts/e2e.test.ts +17 -0
- package/repo-e2e/scripts/e2e.ts +5 -1
- package/repo-e2e/scripts/runE2e.ts +3 -15
- package/repo-e2e/scripts/runE2eArgs.test.ts +21 -17
- package/repo-e2e/scripts/runE2eArgs.ts +16 -29
- package/repo-e2e/scripts/startE2eVona.ts +20 -9
- package/repo-e2e/specs/a-commerce.spec.ts +232 -31
- package/repo-e2e/specs/cabloy-basic.spec.ts +39 -12
- package/repo-e2e/specs/home-user-account.spec.ts +9 -2
- package/scripts/upgrade.test.ts +147 -0
- package/scripts/upgrade.ts +49 -27
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +51 -5
- package/vona/packages-cli/cli-set-api/src/lib/command/bin.dev.ts +6 -1
- package/vona/packages-cli/cli-set-api/test/binDevMode.test.ts +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +41 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/e2eFixture.ts +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/lib/e2eFixture.ts +224 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/e2eFixture.ts +694 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/e2eFixture.test.ts +434 -0
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { basename, resolve } from 'node:path';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
isPlanningCompleteEligible,
|
|
7
|
+
isPrerequisiteSatisfied,
|
|
8
|
+
markdownLines,
|
|
9
|
+
parseAtpIds,
|
|
10
|
+
parseProgress,
|
|
11
|
+
parseWbs,
|
|
12
|
+
} from './spec-parser.mjs';
|
|
6
13
|
export { parseAtpIds, parseProgress, parseWbs } from './spec-parser.mjs';
|
|
7
14
|
|
|
8
15
|
const STATUS_COLORS = {
|
|
9
16
|
'not-started': 'var(--blue)',
|
|
10
17
|
'in-progress': 'var(--orange)',
|
|
18
|
+
'planning-complete': 'var(--teal)',
|
|
11
19
|
'implementation-complete': 'var(--violet)',
|
|
12
20
|
'verified': 'var(--aqua)',
|
|
13
21
|
'blocked': 'var(--red)',
|
|
@@ -47,6 +55,7 @@ const COPY = {
|
|
|
47
55
|
statuses: {
|
|
48
56
|
'not-started': 'Not started',
|
|
49
57
|
'in-progress': 'In progress',
|
|
58
|
+
'planning-complete': 'Planning complete',
|
|
50
59
|
'implementation-complete': 'Implementation complete',
|
|
51
60
|
'verified': 'Verified',
|
|
52
61
|
'blocked': 'Blocked',
|
|
@@ -104,6 +113,7 @@ const COPY = {
|
|
|
104
113
|
statuses: {
|
|
105
114
|
'not-started': '未开始',
|
|
106
115
|
'in-progress': '进行中',
|
|
116
|
+
'planning-complete': '规划完成',
|
|
107
117
|
'implementation-complete': '实施完成',
|
|
108
118
|
'verified': '已核验',
|
|
109
119
|
'blocked': '受阻',
|
|
@@ -248,6 +258,11 @@ export function createChartModel({ readme, wbs, progress, testPlan }) {
|
|
|
248
258
|
}
|
|
249
259
|
task.recordedStatus = task.status;
|
|
250
260
|
task.status = progressStatus;
|
|
261
|
+
if (task.status === 'planning-complete' && !isPlanningCompleteEligible(task)) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`${task.id} has planning-complete status without Completion mode: planning-only.`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
251
266
|
if (task.deferred && task.status !== 'deferred') {
|
|
252
267
|
throw new Error(
|
|
253
268
|
`${task.id} is marked deferred in pdp-wbs.md but has progress status ${task.status}.`,
|
|
@@ -274,10 +289,10 @@ export function createChartModel({ readme, wbs, progress, testPlan }) {
|
|
|
274
289
|
};
|
|
275
290
|
}
|
|
276
291
|
|
|
277
|
-
function style() {
|
|
292
|
+
function style(planningComplete) {
|
|
278
293
|
return `<style>
|
|
279
|
-
:root { --surface:#fcfcfb;--page:#f9f9f7;--ink:#0b0b0b;--secondary:#52514e;--muted:#898781;--grid:#e1e0d9;--axis:#c3c2b7;--blue:#2a78d6;--orange:#eb6834
|
|
280
|
-
@media (prefers-color-scheme:dark) { :root { --surface:#1a1a19;--page:#0d0d0d;--ink:#fff;--secondary:#c3c2b7;--muted:#a09f99;--grid:#2c2c2a;--axis:#383835;--blue:#3987e5;--orange:#d95926
|
|
294
|
+
:root { --surface:#fcfcfb;--page:#f9f9f7;--ink:#0b0b0b;--secondary:#52514e;--muted:#898781;--grid:#e1e0d9;--axis:#c3c2b7;--blue:#2a78d6;--orange:#eb6834;${planningComplete ? '--teal:#087f83;' : ''}--aqua:#1baf7a;--violet:#4a3aa7;--red:#e34948;--yellow:#eda100;--blue-wash:#eaf3fd;--aqua-wash:#e7f7f0; }
|
|
295
|
+
@media (prefers-color-scheme:dark) { :root { --surface:#1a1a19;--page:#0d0d0d;--ink:#fff;--secondary:#c3c2b7;--muted:#a09f99;--grid:#2c2c2a;--axis:#383835;--blue:#3987e5;--orange:#d95926;${planningComplete ? '--teal:#29aeb0;' : ''}--aqua:#199e70;--violet:#9085e9;--red:#e66767;--yellow:#c98500;--blue-wash:#172435;--aqua-wash:#122b23; } }
|
|
281
296
|
.canvas{fill:var(--page)}.card{fill:var(--surface);stroke:var(--grid);stroke-width:1}.title{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:27px;font-weight:700}.subtitle{fill:var(--secondary);font-family:Arial,Helvetica,sans-serif;font-size:14px}.heading{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:13px;font-weight:700}.phase{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:10px;font-weight:700}.task{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:10px}.small{fill:var(--muted);font-family:Arial,Helvetica,sans-serif;font-size:9px}.caption{fill:var(--secondary);font-family:Arial,Helvetica,sans-serif;font-size:11px}.num{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:700;font-variant-numeric:tabular-nums}.grid{stroke:var(--grid);stroke-width:1;shape-rendering:crispEdges}.axis{stroke:var(--axis);stroke-width:1;shape-rendering:crispEdges}.row:focus{outline:2px solid var(--ink);outline-offset:2px}
|
|
282
297
|
</style>`;
|
|
283
298
|
}
|
|
@@ -302,13 +317,13 @@ function renderChartTextLines(className, x, y, lines, lineHeight) {
|
|
|
302
317
|
return `<text class="${className}" x="${x}" y="${y}">${lines.map((line, lineIndex) => `<tspan x="${x}" dy="${lineIndex ? lineHeight : 0}">${escapeXml(line)}</tspan>`).join('')}</text>`;
|
|
303
318
|
}
|
|
304
319
|
|
|
305
|
-
function svgDocument(title, desc, body, height, metadata) {
|
|
320
|
+
function svgDocument(title, desc, body, height, metadata, planningComplete = false) {
|
|
306
321
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
307
322
|
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="${height}" viewBox="0 0 1600 ${height}" role="img" aria-labelledby="chart-title chart-desc">
|
|
308
323
|
<title id="chart-title">${escapeXml(title)}</title>
|
|
309
324
|
<desc id="chart-desc">${escapeXml(desc)}</desc>
|
|
310
325
|
<metadata>${escapeXml(metadata)}</metadata>
|
|
311
|
-
${style()}
|
|
326
|
+
${style(planningComplete)}
|
|
312
327
|
${body}
|
|
313
328
|
</svg>
|
|
314
329
|
`;
|
|
@@ -392,9 +407,12 @@ export function renderGantt(model, suiteName) {
|
|
|
392
407
|
},
|
|
393
408
|
)
|
|
394
409
|
.join('\n');
|
|
395
|
-
const completed = new Set(
|
|
410
|
+
const completed = new Set(
|
|
411
|
+
tasks.filter(task => isPrerequisiteSatisfied(task, task.status)).map(task => task.id),
|
|
412
|
+
);
|
|
396
413
|
const nextTask = tasks.find(
|
|
397
414
|
task =>
|
|
415
|
+
task.status !== 'planning-complete' &&
|
|
398
416
|
task.status !== 'verified' &&
|
|
399
417
|
task.status !== 'deferred' &&
|
|
400
418
|
task.status !== 'waived' &&
|
|
@@ -418,6 +436,7 @@ export function renderGantt(model, suiteName) {
|
|
|
418
436
|
body,
|
|
419
437
|
height,
|
|
420
438
|
t.metadata,
|
|
439
|
+
tasks.some(task => task.status === 'planning-complete'),
|
|
421
440
|
);
|
|
422
441
|
}
|
|
423
442
|
|
|
@@ -152,6 +152,88 @@ test('creates a mixed-status model with deferred scope separated', () => {
|
|
|
152
152
|
assert.equal(model.activeTasks.length, 2);
|
|
153
153
|
assert.equal(model.deferredTasks.length, 1);
|
|
154
154
|
assert.equal(model.verified, 0);
|
|
155
|
+
assert.doesNotMatch(renderGantt(model, 'Demo'), /--teal:/);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('planning completion requires an explicit task mode and preserves verified-only metrics', () => {
|
|
159
|
+
const wbs = files['pdp-wbs.md'].replace(
|
|
160
|
+
'#### WBS-10-01: Freeze the baseline',
|
|
161
|
+
'#### WBS-10-01: Freeze the baseline\n\nCompletion mode: planning-only.',
|
|
162
|
+
);
|
|
163
|
+
const progress = files['progress.md'].replace('`not-started`', '`planning-complete`');
|
|
164
|
+
assert.throws(
|
|
165
|
+
() =>
|
|
166
|
+
createChartModel({
|
|
167
|
+
readme: files['README.md'],
|
|
168
|
+
wbs: files['pdp-wbs.md'],
|
|
169
|
+
progress,
|
|
170
|
+
testPlan: files['test-plan.md'],
|
|
171
|
+
}),
|
|
172
|
+
/planning-complete status without Completion mode/,
|
|
173
|
+
);
|
|
174
|
+
const model = createChartModel({
|
|
175
|
+
readme: files['README.md'],
|
|
176
|
+
wbs,
|
|
177
|
+
progress,
|
|
178
|
+
testPlan: files['test-plan.md'],
|
|
179
|
+
});
|
|
180
|
+
assert.equal(model.tasks[0].completionMode, 'planning-only');
|
|
181
|
+
assert.equal(model.verified, 0);
|
|
182
|
+
const gantt = renderGantt(model, 'Demo');
|
|
183
|
+
assert.match(gantt, /Planning complete/);
|
|
184
|
+
assert.match(gantt, /--teal:#087f83/);
|
|
185
|
+
assert.doesNotMatch(renderBurndown(model, 'Demo'), /--teal:/);
|
|
186
|
+
assert.match(gantt, /class="caption" x="96" y="195">WBS-20-01: Deliver/);
|
|
187
|
+
assert.doesNotMatch(gantt, /class="caption" x="96" y="195">WBS-10-01/);
|
|
188
|
+
assert.match(renderBurndown(model, 'Demo'), />2 remaining · 0 verified</);
|
|
189
|
+
const blocked = createChartModel({
|
|
190
|
+
readme: files['README.md'],
|
|
191
|
+
wbs,
|
|
192
|
+
progress: progress.replace('`in-progress`', '`blocked`'),
|
|
193
|
+
testPlan: files['test-plan.md'],
|
|
194
|
+
});
|
|
195
|
+
assert.match(renderGantt(blocked, 'Demo'), /No dependency-ready WBS candidate is recorded/);
|
|
196
|
+
assert.equal(blocked.verified, 0);
|
|
197
|
+
const chinese = createChartModel({
|
|
198
|
+
readme: '# 中文规划\n\n完整中文记录。',
|
|
199
|
+
wbs,
|
|
200
|
+
progress,
|
|
201
|
+
testPlan: files['test-plan.md'],
|
|
202
|
+
});
|
|
203
|
+
assert.match(renderGantt(chinese, '示例'), /规划完成/);
|
|
204
|
+
assert.match(renderBurndown(chinese, '示例'), /2 剩余 · 0 已核验/);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('completion mode must be unique, supported and task-local', () => {
|
|
208
|
+
const wbs = files['pdp-wbs.md'];
|
|
209
|
+
for (const value of ['unknown.', '', 'planning-only.\nCompletion mode: planning-only.']) {
|
|
210
|
+
assert.throws(
|
|
211
|
+
() => parseWbs(wbs.replace('Tasks:', `Completion mode: ${value}\n\nTasks:`)),
|
|
212
|
+
/Completion mode/,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
assert.throws(
|
|
216
|
+
() =>
|
|
217
|
+
parseWbs(
|
|
218
|
+
wbs.replace('Dependencies: none.', 'Dependencies: none.\nCompletion mode: planning-only.'),
|
|
219
|
+
),
|
|
220
|
+
/Completion mode must belong to a formal WBS task/,
|
|
221
|
+
);
|
|
222
|
+
assert.throws(
|
|
223
|
+
() => parseWbs(`Completion mode: planning-only.\n${wbs}`),
|
|
224
|
+
/Completion mode must belong to a formal WBS task/,
|
|
225
|
+
);
|
|
226
|
+
const progress = files['progress.md'].replace('`not-started`', '`planning-complete`');
|
|
227
|
+
assert.throws(
|
|
228
|
+
() =>
|
|
229
|
+
createChartModel({
|
|
230
|
+
readme: files['README.md'],
|
|
231
|
+
wbs: wbs.replace('Tasks:', 'Status: in-progress.\nCompletion mode: planning-only.\nTasks:'),
|
|
232
|
+
progress,
|
|
233
|
+
testPlan: files['test-plan.md'],
|
|
234
|
+
}),
|
|
235
|
+
/explicit.*Status in-progress.*planning-complete/,
|
|
236
|
+
);
|
|
155
237
|
});
|
|
156
238
|
|
|
157
239
|
test('selects the earliest dependency-ready WBS item', async t => {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export const STATUS_ORDER = [
|
|
3
3
|
'not-started',
|
|
4
4
|
'in-progress',
|
|
5
|
+
'planning-complete',
|
|
5
6
|
'implementation-complete',
|
|
6
7
|
'verified',
|
|
7
8
|
'blocked',
|
|
@@ -306,6 +307,16 @@ function normalizeStatus(value) {
|
|
|
306
307
|
return normalized;
|
|
307
308
|
}
|
|
308
309
|
|
|
310
|
+
export function isPlanningCompleteEligible(task) {
|
|
311
|
+
return task.completionMode === 'planning-only';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function isPrerequisiteSatisfied(task, status) {
|
|
315
|
+
return (
|
|
316
|
+
status === 'verified' || (status === 'planning-complete' && isPlanningCompleteEligible(task))
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
309
320
|
function reviewDateFromLine(line) {
|
|
310
321
|
return (
|
|
311
322
|
line.match(/(?:last reviewed|最后审查日期)\s*(?:[::]\s*)?(\d{4}-\d{2}-\d{2})/i)?.[1] ?? null
|
|
@@ -360,7 +371,7 @@ export function parseProgress(markdown, { file } = {}) {
|
|
|
360
371
|
}
|
|
361
372
|
|
|
362
373
|
function labeledValue(record, labels) {
|
|
363
|
-
const match = record.text.trim().match(/^([A-Z]+)\s*:(.*)$/i);
|
|
374
|
+
const match = record.text.trim().match(/^([A-Z]+(?:\s+[A-Z]+)*)\s*:(.*)$/i);
|
|
364
375
|
return match && labels.includes(match[1].toLowerCase()) ? match[2].trim() : null;
|
|
365
376
|
}
|
|
366
377
|
|
|
@@ -502,6 +513,11 @@ export function parseWbs(markdown, { file } = {}) {
|
|
|
502
513
|
task = null;
|
|
503
514
|
inPhase = false;
|
|
504
515
|
}
|
|
516
|
+
if (!task && labeledValue(record, ['completion mode']) !== null) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
`Completion mode must belong to a formal WBS task at ${atLocation(record, file)}.`,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
505
521
|
if (!inPhase) continue;
|
|
506
522
|
if (task) task.body.push(record);
|
|
507
523
|
else if (!phase.tasks.length) phase.preamble.push(record);
|
|
@@ -519,6 +535,29 @@ export function parseWbs(markdown, { file } = {}) {
|
|
|
519
535
|
? { line: dependency.line, ...(file ? { file } : {}) }
|
|
520
536
|
: undefined;
|
|
521
537
|
task.phase = { number: phase.number, title: phase.title };
|
|
538
|
+
const modeRecords = task.body.filter(
|
|
539
|
+
record => labeledValue(record, ['completion mode']) !== null,
|
|
540
|
+
);
|
|
541
|
+
if (modeRecords.length > 1) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
`Multiple ${task.id} Completion mode fields at ${atLocation(modeRecords[1], file)}.`,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
const mode = modeRecords.length
|
|
547
|
+
? labeledValue(modeRecords[0], ['completion mode'])
|
|
548
|
+
.replace(/`/g, '')
|
|
549
|
+
.replace(/\.$/, '')
|
|
550
|
+
.toLowerCase()
|
|
551
|
+
: undefined;
|
|
552
|
+
if (mode && mode !== 'planning-only') {
|
|
553
|
+
throw new Error(
|
|
554
|
+
`Unsupported ${task.id} Completion mode at ${atLocation(modeRecords[0], file)}.`,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
if (modeRecords.length && !mode) {
|
|
558
|
+
throw new Error(`Empty ${task.id} Completion mode at ${atLocation(modeRecords[0], file)}.`);
|
|
559
|
+
}
|
|
560
|
+
task.completionMode = mode;
|
|
522
561
|
const statusRecords = task.body.filter(record => labeledValue(record, ['status']) !== null);
|
|
523
562
|
if (statusRecords.length > 1) {
|
|
524
563
|
throw new Error(
|
|
@@ -53,6 +53,7 @@ For the detailed authority table, identifiers, and chart boundary, see [Generate
|
|
|
53
53
|
|
|
54
54
|
**Evidence-gated delivery** means delivery status stays precise about what is known and what has been proven.
|
|
55
55
|
|
|
56
|
+
- `planning-complete` means an explicitly `planning-only` WBS task's documentary checks have a named-reviewer, revision-scoped closure disposition and retained planning proof. It satisfies that task's prerequisite edge, not implementation, ATP, successor approval, or release.
|
|
56
57
|
- `implementation-complete` means the source work is complete, while required ATP or release proof remains.
|
|
57
58
|
- `verified` means all applicable WBS checks and ATPs have durable, linked, redacted observed evidence.
|
|
58
59
|
|
|
@@ -109,11 +109,12 @@ Use the narrowest approved check first, then follow the linked ATP procedures an
|
|
|
109
109
|
|
|
110
110
|
Use status precisely:
|
|
111
111
|
|
|
112
|
-
- `in-progress` — approved
|
|
112
|
+
- `in-progress` — approved work or verification has actually begun
|
|
113
|
+
- `planning-complete` — only an opted-in documentary/design WBS task with a named-reviewer, revision-scoped closure disposition and linked planning proof; no source or ATP closure
|
|
113
114
|
- `implementation-complete` — source work is complete, but required ATP or release proof remains
|
|
114
115
|
- `verified` — all applicable WBS checks and ATPs have durable, linked, redacted observed evidence
|
|
115
116
|
|
|
116
|
-
A successful build, generation command, hook, manual walkthrough, screenshot, or unrelated test run is not by itself `verified` unless the authoritative test plan defines it as sufficient retained proof. Evidence is revision- and authority-scoped; mark old proof superseded or requiring rerun when relevant source or authority changes.
|
|
117
|
+
`planning-complete` satisfies a predecessor dependency edge but does not unblock a task still recorded `blocked` or grant an execution dossier. Charts count only `verified` toward verified/remaining metrics. A successful build, generation command, hook, manual walkthrough, screenshot, or unrelated test run is not by itself `verified` unless the authoritative test plan defines it as sufficient retained proof. Evidence is revision- and authority-scoped; mark old proof superseded or requiring rerun when relevant source or authority changes.
|
|
117
118
|
|
|
118
119
|
## Refresh derived charts last
|
|
119
120
|
|
|
@@ -115,7 +115,7 @@ PRD → SRS → WBS → ATP → Evidence
|
|
|
115
115
|
|
|
116
116
|
A product or technical change belongs in its PRD, SRS, or accepted ADR before its WBS, acceptance, progress, evidence, and chart implications are updated. A progress entry or chart cannot introduce a requirement, resolve a contract conflict, or accept an ADR.
|
|
117
117
|
|
|
118
|
-
Planning records and derived charts do not establish `implementation-complete` or `verified`. `verified` requires the applicable acceptance procedure and retained, redacted observed evidence. A generated plan, planned command, scaffold, screenshot, or unrelated check is not automatically sufficient proof.
|
|
118
|
+
A WBS task may declare `Completion mode: planning-only.` only for documentary/design-only checks. It can reach `planning-complete` after an explicit named-reviewer, revision-scoped closure disposition and linked planning proof; this satisfies its dependency edge without executing source, passing future ATPs, unblocking a recorded `blocked` successor, or counting as `verified` in charts. Planning records and derived charts do not establish `implementation-complete` or `verified`. `verified` requires the applicable acceptance procedure and retained, redacted observed evidence. A generated plan, planned command, scaffold, screenshot, or unrelated check is not automatically sufficient proof.
|
|
119
119
|
|
|
120
120
|
## Check planning without claiming implementation
|
|
121
121
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/repo-docs/demo.md
CHANGED
|
@@ -10,3 +10,17 @@ Explore the Cabloy demonstration sites:
|
|
|
10
10
|
| ---------- | ---------------------------------------------------------------- |
|
|
11
11
|
| Web site | [https://demo.cabloy.com/](https://demo.cabloy.com/) |
|
|
12
12
|
| Admin site | [https://demo.cabloy.com/admin/](https://demo.cabloy.com/admin/) |
|
|
13
|
+
|
|
14
|
+
## Videos
|
|
15
|
+
|
|
16
|
+
### 1. Can an Admin Site Use SSR? CabloyJS in Three Practical Demos (Duration: 1:16)
|
|
17
|
+
|
|
18
|
+
[](https://youtu.be/786IQhRdr1I)
|
|
19
|
+
|
|
20
|
+
### 2. A First: Second-Level Tabs for Admin Multitasking (Duration: 1:50)
|
|
21
|
+
|
|
22
|
+
[](https://youtu.be/L6DxD-JfztQ)
|
|
23
|
+
|
|
24
|
+
### 3. AI Coding in Action: One Prompt for CRUD, SSR & Multitasking (Duration: 3:50)
|
|
25
|
+
|
|
26
|
+
[](https://youtu.be/PV9_By08hDM)
|
|
@@ -68,23 +68,16 @@ npm run deps:vona
|
|
|
68
68
|
npm run test:e2e cabloy-basic home-user-account
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The managed
|
|
72
|
-
|
|
73
|
-
The unified runner uses two commands:
|
|
74
|
-
|
|
75
|
-
```text
|
|
76
|
-
npm run test:e2e clean local run with database reset
|
|
77
|
-
npm run test:e2e:fast fast run without automatic reset
|
|
78
|
-
```
|
|
71
|
+
The sole managed `npm run test:e2e` command starts a fresh local Vona worker with `--workers=1 --flavor=normal --mode=test`, then runs the selected specs against Vona integrated SSR. Test-mode startup initializes test resources without a separate `db:reset`; browser commands consume already-built artifacts and do not rebuild them. Confirm exclusive ownership of the effective test database, `_local` Redis namespace, public/runtime paths, `APP_NAME`, API origin, and listener before startup; a free port alone is insufficient.
|
|
79
72
|
|
|
80
73
|
Place flat spec basenames directly after the npm script name. Use npm's `--` delimiter before Playwright options. With no names, every spec in `repo-e2e/specs` runs. Native `--grep` and `--grep-invert` remain available, and repeatable `--tag` values require all listed tags:
|
|
81
74
|
|
|
82
75
|
```bash
|
|
83
76
|
npm run test:e2e cabloy-basic home-user-account -- --grep @flow
|
|
84
|
-
npm run test:e2e
|
|
77
|
+
npm run test:e2e a-commerce -- --tag @web --tag @smoke
|
|
85
78
|
```
|
|
86
79
|
|
|
87
|
-
For the complete tag vocabulary
|
|
80
|
+
For the complete Basic tag vocabulary and managed-runner argument boundaries, see [Repo Scripts](/reference/repo-scripts#ssr-browser-checks). `E2E_BASE_URL` must be unset: externally managed targets are unsupported.
|
|
88
81
|
|
|
89
82
|
## Cabloy Start root wrappers
|
|
90
83
|
|
|
@@ -102,7 +95,7 @@ Cabloy Start does not expose the Basic Commerce wrapper family.
|
|
|
102
95
|
|
|
103
96
|
## Start SSR browser acceptance
|
|
104
97
|
|
|
105
|
-
The Start Web and Admin browser baseline
|
|
98
|
+
The Start Web and Admin browser baseline exercises Vona integrated SSR dispatch at the effective local test-mode server port. Prepare current SSR and REST artifacts, then use the managed local command:
|
|
106
99
|
|
|
107
100
|
```bash
|
|
108
101
|
npm run build:zova
|
|
@@ -110,23 +103,22 @@ npm run deps:vona
|
|
|
110
103
|
npm run test:e2e
|
|
111
104
|
```
|
|
112
105
|
|
|
113
|
-
Start uses
|
|
106
|
+
Start uses one E2E command and one fresh managed local test-mode lifecycle:
|
|
114
107
|
|
|
115
108
|
```text
|
|
116
|
-
npm run test:e2e managed
|
|
117
|
-
npm run test:e2e:fast fast rerun without an automatic reset
|
|
109
|
+
npm run test:e2e fresh managed local test-mode suite run
|
|
118
110
|
```
|
|
119
111
|
|
|
120
112
|
Select flat spec basenames and filter scenarios or surfaces with runner tags and native Playwright arguments. Current Start scenarios use tags including `@web`, `@admin`, `@smoke`, `@layout`, `@cabloy-admin`, `@account`, `@ssr`, and `@flow`:
|
|
121
113
|
|
|
122
114
|
```bash
|
|
123
115
|
npm run test:e2e cabloy-start
|
|
124
|
-
npm run test:e2e
|
|
125
|
-
npm run test:e2e
|
|
126
|
-
npm run test:e2e
|
|
116
|
+
npm run test:e2e cabloy-start -- --tag @web
|
|
117
|
+
npm run test:e2e cabloy-admin -- --tag @admin --tag @cabloy-admin
|
|
118
|
+
npm run test:e2e cabloy-start -- --grep ATP-START-FLOW-01
|
|
127
119
|
```
|
|
128
120
|
|
|
129
|
-
|
|
121
|
+
Start does not support externally managed E2E targets: `E2E_BASE_URL` must be unset. The command requires the configured local port to be available and starts one Vona worker with `--flavor=normal --mode=test`; Playwright does not reuse an existing process. Test-mode startup initializes test state. Confirm exclusive ownership of the test database, Redis namespace, and public/runtime paths before running it. The command does not replace the paired build/dependency-sync prerequisite; do not change shared environment identity or ports to avoid a busy resource.
|
|
130
122
|
|
|
131
123
|
## Zova script model
|
|
132
124
|
|
|
@@ -77,12 +77,12 @@ For privacy, the skill writes only to absent or empty permitted broad local file
|
|
|
77
77
|
|
|
78
78
|
Use commands from the active repository root.
|
|
79
79
|
|
|
80
|
-
| Edition | Marker | Frontend command | Managed
|
|
80
|
+
| Edition | Marker | Frontend command | Managed E2E |
|
|
81
81
|
| ------------ | ------------------ | -------------------------------------------------- | ------------------ |
|
|
82
82
|
| Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
|
|
83
83
|
| Cabloy Start | `__CABLOY_START__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
|
|
84
84
|
|
|
85
|
-
Run one frontend command, not both, in each worktree. Both managed
|
|
85
|
+
Run one frontend command, not both, in each worktree. Both managed E2E workflows read Vona's effective local `SERVER_LISTEN_PORT` and start one fresh Vona test-mode worker; startup initializes test resources without a separate reset. Before either command, confirm exclusive ownership of the effective test database, `_local` Redis namespace, public/runtime paths, `APP_NAME`, API origin, and listener. A free port alone does not establish ownership. Do not change shared identity or ports to bypass a collision, and leave `E2E_BASE_URL` unset: externally managed targets are unsupported.
|
|
86
86
|
|
|
87
87
|
## What this isolates
|
|
88
88
|
|
package/repo-docs/index.md
CHANGED
|
@@ -32,9 +32,9 @@ features:
|
|
|
32
32
|
|
|
33
33
|
## Demonstrations(Videos)
|
|
34
34
|
|
|
35
|
-
### 1.
|
|
35
|
+
### 1. AI Coding in Action: One Prompt for CRUD, SSR & Multitasking (Duration: 3:50)
|
|
36
36
|
|
|
37
|
-
[](https://youtu.be/PV9_By08hDM)
|
|
38
38
|
|
|
39
39
|
## Choose a reading path
|
|
40
40
|
|
|
@@ -40,9 +40,7 @@ Cabloy Basic exposes these shared root scripts:
|
|
|
40
40
|
- `npm run start`
|
|
41
41
|
- `npm run start:one`
|
|
42
42
|
- `npm run test`
|
|
43
|
-
- `npm run db:reset`
|
|
44
43
|
- `npm run test:e2e`
|
|
45
|
-
- `npm run test:e2e:fast`
|
|
46
44
|
- `npm run tsc`
|
|
47
45
|
- `npm run docs:dev`
|
|
48
46
|
- `npm run docs:build`
|
|
@@ -117,7 +115,6 @@ Cabloy Start exposes the equivalent Start repository surface:
|
|
|
117
115
|
- `npm run test`
|
|
118
116
|
- `npm run db:reset`
|
|
119
117
|
- `npm run test:e2e`
|
|
120
|
-
- `npm run test:e2e:fast`
|
|
121
118
|
- `npm run tsc`
|
|
122
119
|
|
|
123
120
|
Cabloy Start does not expose Basic Commerce or root documentation wrappers.
|
|
@@ -138,7 +135,7 @@ repo-e2e/specs/home-user-account.spec.ts
|
|
|
138
135
|
repo-e2e/specs/a-commerce.spec.ts
|
|
139
136
|
```
|
|
140
137
|
|
|
141
|
-
It
|
|
138
|
+
It reconciles the single framework-owned `test:e2e` script and the `@playwright/test` development dependency. The retired `test:e2e:fast` script is removed only if its value matches the known old framework command; a customized project alias is preserved. Keep additional project browser specs under other filenames in `repo-e2e/specs`; the upgrader updates only the listed framework files. The current fresh baseline is required and is not repaired for unsupported legacy project layouts.
|
|
142
139
|
|
|
143
140
|
### Cabloy Start repository
|
|
144
141
|
|
|
@@ -154,31 +151,28 @@ The public-package upgrade flow does not source or reconcile the Start-owned bas
|
|
|
154
151
|
|
|
155
152
|
## SSR browser checks
|
|
156
153
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
- `npm run test:e2e`: clean local run; checks the managed Vona port, resets the database, starts one development Vona worker, and runs Playwright.
|
|
160
|
-
- `npm run test:e2e:fast`: skips the reset for quick reruns and may target either the local managed Vona server or an externally managed `E2E_BASE_URL`.
|
|
154
|
+
Cabloy Basic and Cabloy Start each use a single managed local `npm run test:e2e` command. In Basic, the runner checks the effective `normal/test/local` Vona listener port and starts one fresh `--workers=1 --flavor=normal --mode=test` worker. Test-mode startup initializes test resources; the runner does not invoke a separate `db:reset` or a development-mode server. Playwright does not reuse an existing process. Confirm exclusive ownership of the effective test database, `_local` Redis namespace, public/runtime paths, `APP_NAME`, API origin, and listener before starting it; port availability alone is insufficient.
|
|
161
155
|
|
|
162
|
-
|
|
156
|
+
Both editions' browser checks target Vona integrated SSR. In the Cabloy Basic default environment, the Vona listener is `7102`; the Zova standalone SSR development server uses `9000` and is not an acceptance target.
|
|
163
157
|
|
|
164
158
|
Place spec basenames directly after the npm script name; use npm's `--` delimiter only before Playwright options. Multiple spec names are allowed. With no names, every spec in `repo-e2e/specs` is discovered:
|
|
165
159
|
|
|
166
160
|
```bash
|
|
167
161
|
npm run test:e2e cabloy-basic home-user-account
|
|
168
162
|
npm run test:e2e a-commerce
|
|
169
|
-
npm run test:e2e
|
|
170
|
-
npm run test:e2e
|
|
171
|
-
npm run test:e2e
|
|
163
|
+
npm run test:e2e home-user-account
|
|
164
|
+
npm run test:e2e a-commerce -- --grep ATP-SSR
|
|
165
|
+
npm run test:e2e a-commerce -- --grep-invert @admin
|
|
172
166
|
```
|
|
173
167
|
|
|
174
168
|
Tags remain independent from filenames. Repeat `--tag` to require all tags, while native `--grep` and `--grep-invert` remain available:
|
|
175
169
|
|
|
176
170
|
```bash
|
|
177
|
-
npm run test:e2e
|
|
178
|
-
npm run test:e2e
|
|
171
|
+
npm run test:e2e a-commerce -- --tag @web --tag @smoke
|
|
172
|
+
npm run test:e2e home-user-account -- --grep @flow --tag @web
|
|
179
173
|
```
|
|
180
174
|
|
|
181
|
-
The existing tags include `@web`, `@admin`, `@smoke`, `@flow`, `@ssr`, `@theme`, and the business tags used by Commerce such as `@cart`, `@payment`, `@shipment`, and `@refund`. No suite tag is required.
|
|
175
|
+
The existing Basic tags include `@web`, `@admin`, `@smoke`, `@flow`, `@ssr`, `@theme`, and the business tags used by Commerce such as `@cart`, `@payment`, `@shipment`, and `@refund`. No suite tag is required. In Cabloy Basic, `E2E_BASE_URL` must be unset: external targets and a fast/no-reset mode are unsupported.
|
|
182
176
|
|
|
183
177
|
### Cabloy Basic and Commerce
|
|
184
178
|
|
|
@@ -198,15 +192,9 @@ npm run deps:vona
|
|
|
198
192
|
npm run test:e2e a-commerce
|
|
199
193
|
```
|
|
200
194
|
|
|
201
|
-
For a separately managed target, use the fast command:
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast a-commerce -- --tag @smoke
|
|
205
|
-
```
|
|
206
|
-
|
|
207
195
|
### Cabloy Start
|
|
208
196
|
|
|
209
|
-
The Start suite exercises Web at `/` and Admin at `/admin` through Vona integrated SSR dispatch. Prepare current Start artifacts before a managed local run:
|
|
197
|
+
The Start suite exercises Web at `/` and Admin at `/admin` through Vona integrated SSR dispatch. Prepare current Start artifacts before a managed local run. The runner uses the effective local `normal`-flavor, test-mode Vona listener, rather than assuming a fixed port:
|
|
210
198
|
|
|
211
199
|
```bash
|
|
212
200
|
npm run build:zova
|
|
@@ -216,20 +204,16 @@ npm run test:e2e
|
|
|
216
204
|
|
|
217
205
|
```bash
|
|
218
206
|
# Exact acceptance scenario
|
|
219
|
-
npm run test:e2e
|
|
207
|
+
npm run test:e2e cabloy-start -- --grep ATP-START-FLOW-01
|
|
220
208
|
|
|
221
209
|
# Category or surface selection
|
|
222
|
-
npm run test:e2e
|
|
223
|
-
npm run test:e2e
|
|
210
|
+
npm run test:e2e cabloy-start -- --tag @smoke
|
|
211
|
+
npm run test:e2e cabloy-admin -- --tag @admin --tag @cabloy-admin
|
|
224
212
|
```
|
|
225
213
|
|
|
226
|
-
|
|
214
|
+
In Cabloy Start, the sole `npm run test:e2e` command checks the configured local port and starts a fresh runner-managed Vona target with `--workers=1 --flavor=normal --mode=test`. Playwright never reuses an existing server. Test-mode startup initializes test resources; the runner does not invoke a separate `db:reset`. Confirm ownership of the test database, Redis namespace, and public/runtime paths before running it; do not change shared environment identity or ports to bypass a busy resource.
|
|
227
215
|
|
|
228
|
-
|
|
229
|
-
E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast
|
|
230
|
-
E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast cabloy-admin -- --tag @admin
|
|
231
|
-
E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast cabloy-start -- --tag @web
|
|
232
|
-
```
|
|
216
|
+
`E2E_BASE_URL` is unsupported in Cabloy Start and must be unset, even for a local URL. Externally managed targets are not supported.
|
|
233
217
|
|
|
234
218
|
Browser commands consume existing SSR and REST artifacts; they never rebuild them. Install Chromium once when needed with `npx playwright install chromium`.
|
|
235
219
|
|
|
@@ -2,9 +2,6 @@ import { defineConfig } from '@playwright/test';
|
|
|
2
2
|
|
|
3
3
|
import { E2E_LOCAL_BASE_URL, E2E_ROOT_DIR } from '../scripts/e2e.ts';
|
|
4
4
|
|
|
5
|
-
const externalBaseURL = process.env.E2E_BASE_URL;
|
|
6
|
-
const baseURL = externalBaseURL || E2E_LOCAL_BASE_URL;
|
|
7
|
-
|
|
8
5
|
export default defineConfig({
|
|
9
6
|
testDir: `${E2E_ROOT_DIR}/repo-e2e/specs`,
|
|
10
7
|
fullyParallel: false,
|
|
@@ -13,22 +10,20 @@ export default defineConfig({
|
|
|
13
10
|
retries: process.env.CI ? 2 : 0,
|
|
14
11
|
reporter: process.env.CI ? [['html', { open: 'never' }], ['list']] : 'list',
|
|
15
12
|
use: {
|
|
16
|
-
baseURL,
|
|
13
|
+
baseURL: E2E_LOCAL_BASE_URL,
|
|
17
14
|
trace: 'on-first-retry',
|
|
18
15
|
},
|
|
19
|
-
webServer:
|
|
20
|
-
|
|
21
|
-
:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
},
|
|
16
|
+
webServer: {
|
|
17
|
+
command: 'node repo-e2e/scripts/startE2eVona.ts',
|
|
18
|
+
cwd: E2E_ROOT_DIR,
|
|
19
|
+
url: `${E2E_LOCAL_BASE_URL}/health/ready`,
|
|
20
|
+
timeout: 180_000,
|
|
21
|
+
reuseExistingServer: false,
|
|
22
|
+
stdout: 'pipe',
|
|
23
|
+
stderr: 'pipe',
|
|
24
|
+
gracefulShutdown: {
|
|
25
|
+
signal: 'SIGINT',
|
|
26
|
+
timeout: 10_000,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
34
29
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
test('rejects a defined E2E_BASE_URL before loading local configuration', () => {
|
|
7
|
+
const entrypoint = fileURLToPath(new URL('./e2e.ts', import.meta.url));
|
|
8
|
+
for (const value of ['', 'http://127.0.0.1:7102']) {
|
|
9
|
+
const result = spawnSync(process.execPath, [entrypoint], {
|
|
10
|
+
env: { ...process.env, E2E_BASE_URL: value },
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
});
|
|
13
|
+
assert.notEqual(result.status, 0);
|
|
14
|
+
assert.match(result.stderr, /E2E_BASE_URL is unsupported/);
|
|
15
|
+
assert.doesNotMatch(result.stderr, /SERVER_LISTEN_PORT/);
|
|
16
|
+
}
|
|
17
|
+
});
|
package/repo-e2e/scripts/e2e.ts
CHANGED
|
@@ -4,8 +4,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
|
|
5
5
|
export const E2E_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
6
6
|
|
|
7
|
+
if (process.env.E2E_BASE_URL !== undefined) {
|
|
8
|
+
throw new Error('E2E_BASE_URL is unsupported; the E2E runner manages a local test-mode target.');
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
const vonaEnv = dotenv.loadEnvs(
|
|
8
|
-
{ flavor: 'normal', mode: '
|
|
12
|
+
{ flavor: 'normal', mode: 'test', local: 'local' },
|
|
9
13
|
resolve(E2E_ROOT_DIR, 'vona', 'env'),
|
|
10
14
|
'.env',
|
|
11
15
|
);
|