@namewta/speculo 0.8.5 → 0.8.7
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 +4 -3
- package/dist/src/structured.js +162 -4
- package/dist/src/structured.js.map +1 -1
- package/package.json +2 -2
- package/template/canonical/canonical-specdev-goal-plan.md +14 -15
- package/template/canonical/canonical-specdev-grill-with-docs.md +9 -8
- package/template/canonical/canonical-specdev-spec.md +9 -8
- package/template/canonical/canonical-specdev-tickets.md +13 -14
- package/template/canonical/canonical-specdev-wayfinder.md +7 -7
- package/template/commands/status.md +3 -3
- package/template/skills/archive-and-consolidate/SKILL.md +19 -14
- package/template/workflows/learning/A-archive-and-consolidate/A-archive-and-consolidate.md +38 -0
- package/template/workflows/learning/A-archive-and-consolidate/promotion-plan-template.md +23 -0
- package/template/workflows/learning/A-assess-and-plan/A-assess-and-plan.md +39 -0
- package/template/workflows/learning/A-assess-and-plan/change-status-template.json +28 -0
- package/template/workflows/learning/A-assess-and-plan/learning-plan-template.md +28 -0
- package/template/workflows/learning/E-eli5/E-eli5.md +37 -0
- package/template/workflows/learning/E-eli5/lesson-template.md +31 -0
- package/template/workflows/learning/I-init-setup/I-init-setup.md +36 -0
- package/template/workflows/learning/I-init-setup/context-index-template.md +6 -0
- package/template/workflows/learning/I-init-setup/learner-profile-template.md +17 -0
- package/template/workflows/learning/I-init-setup/review-index-template.md +4 -0
- package/template/workflows/learning/INDEX.md +26 -0
- package/template/workflows/learning/P-practice/P-practice.md +34 -0
- package/template/workflows/learning/P-practice/practice-template.md +16 -0
- package/template/workflows/learning/Q-quiz/Q-quiz.md +34 -0
- package/template/workflows/learning/Q-quiz/quiz-artifact-template.md +16 -0
- package/template/workflows/learning/R-review/R-review.md +35 -0
- package/template/workflows/learning/R-review/review-template.md +12 -0
- package/template/workflows/learning/README.md +121 -0
- package/template/workflows/learning/_state/archive/.gitkeep +1 -0
- package/template/workflows/learning/_state/changes/.gitkeep +1 -0
- package/template/workflows/learning/_state/status.json +6 -0
- package/template/workflows/learning/common/rules/artifact-contract.md +27 -0
- package/template/workflows/learning/common/rules/assessment-policy.md +7 -0
- package/template/workflows/learning/common/rules/knowledge-organization.md +9 -0
- package/template/workflows/learning/common/rules/mastery-policy.md +21 -0
- package/template/workflows/learning/common/rules/path-reference-contract.md +7 -0
- package/template/workflows/learning/common/rules/teaching-policy.md +15 -0
- package/template/workflows/learning/common/schemas/change-status.schema.json +41 -0
- package/template/workflows/learning/common/schemas/status.schema.json +32 -0
- package/template/workflows/learning/common/skills/knowledge-promotion/SKILL.md +46 -0
- package/template/workflows/learning/common/skills/knowledge-promotion/domain-index-template.md +6 -0
- package/template/workflows/learning/common/skills/knowledge-promotion/domain-overview-template.md +19 -0
- package/template/workflows/learning/common/skills/knowledge-promotion/knowledge-template.md +25 -0
- package/template/workflows/learning/common/tools/validate-learning.mjs +356 -0
- package/template/workflows/learning/runtime-contract.json +11 -0
- package/template/workflows/specdev/I-init-setup/I-init-setup.md +1 -1
- package/template/workflows/specdev/I-init-setup/config-template.json +2 -2
- package/template/workflows/specdev/INDEX.md +2 -2
- package/template/workflows/specdev/P-goal-plan/planning-modes.md +1 -1
- package/template/workflows/specdev/P-prototype/P-prototype.md +30 -26
- package/template/workflows/specdev/P-prototype/design-library/INDEX.md +43 -0
- package/template/workflows/specdev/P-prototype/design-library/color-and-theme.md +65 -0
- package/template/workflows/specdev/P-prototype/design-library/foundations.md +66 -0
- package/template/workflows/specdev/P-prototype/design-library/interaction-patterns.md +61 -0
- package/template/workflows/specdev/P-prototype/design-library/product-pattern-index.md +73 -0
- package/template/workflows/specdev/P-prototype/design-library/research-provenance.md +54 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/METHODOLOGY.md +90 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/README.md +31 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/data/extended-projects.json +241 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/data/projects.json +253 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/reference/USAGE.md +40 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/reference/design-tokens.css +145 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/report/OPEN_SOURCE_UI_RESEARCH_2026.md +519 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/research/PROJECT_TEMPLATE.md +29 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/research/README.md +9 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/research/claude-code-modern-clients.md +407 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/research/creative-ai-communication.md +574 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/research/data-dev-tools.md +432 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/research/personal-multiplatform-apps.md +185 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/research/productivity-collaboration.md +398 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/01-dense-ide.html +68 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/02-monochrome-console.html +18 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/03-soft-personal-ai.html +17 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/04-responsive-web.html +14 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/05-mobile-supervisor.html +16 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/06-cross-platform-workspace.html +17 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/07-local-first-content.html +9 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/08-media-first.html +17 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/README.md +49 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/assets/ATTRIBUTION.md +9 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/assets/landscape.jpg +0 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/assets/lucide.js +20494 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/assets/mountain.jpg +0 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/assets/workspace.jpg +0 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/index.html +70 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/package-lock.json +78 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/package.json +12 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/screenshots/index-desktop.png +0 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/scripts/gallery.js +134 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/styles/base.css +795 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/styles/pages.css +533 -0
- package/template/workflows/specdev/P-prototype/design-library/research-snapshot/ui-gallery/tests/gallery.spec.js +56 -0
- package/template/workflows/specdev/P-prototype/design-library/responsive-and-platforms.md +53 -0
- package/template/workflows/specdev/P-prototype/design-library/style-index.md +32 -0
- package/template/workflows/specdev/P-prototype/design-package.schema.json +70 -0
- package/template/workflows/specdev/P-prototype/design-system-template.md +366 -0
- package/template/workflows/specdev/P-prototype/detect-existing-style.md +53 -0
- package/template/workflows/specdev/P-prototype/generate-design-package.md +50 -0
- package/template/workflows/specdev/P-prototype/style-selection-protocol.md +41 -0
- package/template/workflows/specdev/P-prototype/tools/materialize-prototype.mjs +126 -0
- package/template/workflows/specdev/P-prototype/tools/validate-design-package.mjs +155 -0
- package/template/workflows/specdev/README.md +12 -11
- package/template/workflows/specdev/T-triage/T-triage.md +1 -1
- package/template/workflows/specdev/W-wayfinder/W-wayfinder.md +1 -1
- package/template/workflows/specdev/common/README.md +2 -2
- package/template/workflows/specdev/common/rules/artifact-contract.md +3 -2
- package/template/workflows/specdev/common/schemas/config.schema.json +4 -4
- package/template/workflows/specdev/common/skills/dev-worktree/SKILL.md +4 -4
- package/template/workflows/specdev/common/skills/dev-worktree/references/create.md +1 -3
- package/template/workflows/specdev/common/tools/validate-specdev.mjs +83 -92
- package/template/canonical/canonical-specdev-engineering-cognitive-mentor.md +0 -1968
- package/template/workflows/specdev/E-eli5/E-eli5.md +0 -97
- package/template/workflows/specdev/E-engineering-cognitive-mentor/E-engineering-cognitive-mentor.md +0 -254
- package/template/workflows/specdev/E-engineering-cognitive-mentor/architecture-guidance.md +0 -90
- package/template/workflows/specdev/E-engineering-cognitive-mentor/bug-guidance.md +0 -80
- package/template/workflows/specdev/E-engineering-cognitive-mentor/codebase-guidance.md +0 -107
- package/template/workflows/specdev/E-engineering-cognitive-mentor/comprehension-and-closure.md +0 -95
- package/template/workflows/specdev/E-engineering-cognitive-mentor/domain-learning-guidance.md +0 -62
- package/template/workflows/specdev/E-engineering-cognitive-mentor/evidence-and-options.md +0 -132
- package/template/workflows/specdev/E-engineering-cognitive-mentor/interaction-protocol.md +0 -116
- package/template/workflows/specdev/E-engineering-cognitive-mentor/mentor-report-template.md +0 -135
- package/template/workflows/specdev/E-engineering-cognitive-mentor/mode-routing.md +0 -47
- package/template/workflows/specdev/E-engineering-cognitive-mentor/persistence-and-resume.md +0 -132
- package/template/workflows/specdev/E-engineering-cognitive-mentor/requirements-guidance.md +0 -92
- package/template/workflows/specdev/P-prototype/logic-prototype.md +0 -24
- package/template/workflows/specdev/P-prototype/prototype-record-template.md +0 -46
- package/template/workflows/specdev/P-prototype/ui-prototype.md +0 -21
- package/template/workflows/specdev/common/schemas/prototype-record.schema.json +0 -24
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
const REQUIRED_TARGETS = new Set([
|
|
8
|
+
"final/index.html",
|
|
9
|
+
"final/styles.css",
|
|
10
|
+
"final/app.js",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const TARGET_LANGUAGE = new Map([
|
|
14
|
+
["final/index.html", new Set(["html"])],
|
|
15
|
+
["final/styles.css", new Set(["css"])],
|
|
16
|
+
["final/app.js", new Set(["javascript", "js"])],
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export function extractPrototypeSources(markdown) {
|
|
20
|
+
const pattern = /<!-- PROTOTYPE-FILE: ([^\r\n]+) -->\r?\n```(html|css|javascript|js)\r?\n([\s\S]*?)\r?\n```\r?\n<!-- \/PROTOTYPE-FILE -->/g;
|
|
21
|
+
const sources = new Map();
|
|
22
|
+
|
|
23
|
+
for (const match of markdown.matchAll(pattern)) {
|
|
24
|
+
const target = match[1].trim().replaceAll("\\", "/");
|
|
25
|
+
if (isAbsolute(target) || !TARGET_LANGUAGE.has(target)) {
|
|
26
|
+
throw new Error(`unsupported prototype target: ${target}`);
|
|
27
|
+
}
|
|
28
|
+
if (!TARGET_LANGUAGE.get(target).has(match[2])) throw new Error(`invalid code fence language for ${target}: ${match[2]}`);
|
|
29
|
+
if (sources.has(target)) throw new Error(`duplicate prototype target: ${target}`);
|
|
30
|
+
sources.set(target, `${match[3].replaceAll("\r\n", "\n")}\n`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const openingCount = markdown.match(/<!-- PROTOTYPE-FILE:/g)?.length ?? 0;
|
|
34
|
+
const closingCount = markdown.match(/<!-- \/PROTOTYPE-FILE -->/g)?.length ?? 0;
|
|
35
|
+
if (openingCount !== 3 || closingCount !== 3 || sources.size !== 3) {
|
|
36
|
+
throw new Error(`expected exactly three paired PROTOTYPE-FILE blocks; found ${openingCount} openings, ${closingCount} closings, ${sources.size} valid blocks`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const target of REQUIRED_TARGETS) {
|
|
40
|
+
if (!sources.has(target)) throw new Error(`missing prototype source block: ${target}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return sources;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveInside(root, target) {
|
|
47
|
+
const destination = resolve(root, ...target.split("/"));
|
|
48
|
+
const local = relative(root, destination);
|
|
49
|
+
if (!local || local.startsWith(`..${sep}`) || local === ".." || isAbsolute(local)) {
|
|
50
|
+
throw new Error(`prototype target escapes design package: ${target}`);
|
|
51
|
+
}
|
|
52
|
+
return destination;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function assertNoSymlink(root, target) {
|
|
56
|
+
let current = root;
|
|
57
|
+
const local = relative(root, target);
|
|
58
|
+
for (const segment of ["", ...local.split(sep).filter(Boolean)]) {
|
|
59
|
+
if (segment) current = join(current, segment);
|
|
60
|
+
try {
|
|
61
|
+
const info = await lstat(current);
|
|
62
|
+
if (info.isSymbolicLink()) throw new Error(`prototype path must not contain symlinks: ${current}`);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.code !== "ENOENT") throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function atomicWrite(destination, content) {
|
|
70
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
71
|
+
const temporary = `${destination}.tmp-${process.pid}-${Date.now()}`;
|
|
72
|
+
await writeFile(temporary, content, "utf8");
|
|
73
|
+
try {
|
|
74
|
+
await rename(temporary, destination);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
await rm(temporary, { force: true });
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function materialize(markdownPath, { check = false } = {}) {
|
|
82
|
+
const absoluteMarkdown = resolve(markdownPath);
|
|
83
|
+
const root = dirname(absoluteMarkdown);
|
|
84
|
+
const markdown = await readFile(absoluteMarkdown, "utf8");
|
|
85
|
+
const sources = extractPrototypeSources(markdown);
|
|
86
|
+
const mismatches = [];
|
|
87
|
+
|
|
88
|
+
for (const [target, expected] of sources) {
|
|
89
|
+
const destination = resolveInside(root, target);
|
|
90
|
+
await assertNoSymlink(root, destination);
|
|
91
|
+
if (check) {
|
|
92
|
+
let actual;
|
|
93
|
+
try {
|
|
94
|
+
actual = await readFile(destination, "utf8");
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error.code === "ENOENT") {
|
|
97
|
+
mismatches.push(`${target}: missing`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
if (actual !== expected) mismatches.push(`${target}: differs from design-system.md`);
|
|
103
|
+
} else {
|
|
104
|
+
await atomicWrite(destination, expected);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (mismatches.length > 0) throw new Error(mismatches.join("\n"));
|
|
109
|
+
return [...sources.keys()];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function main() {
|
|
113
|
+
const args = process.argv.slice(2);
|
|
114
|
+
const check = args.includes("--check");
|
|
115
|
+
const paths = args.filter((arg) => arg !== "--check");
|
|
116
|
+
if (paths.length !== 1) throw new Error("usage: materialize-prototype.mjs [--check] <design-system.md>");
|
|
117
|
+
const targets = await materialize(paths[0], { check });
|
|
118
|
+
console.log(`${check ? "verified" : "materialized"} ${targets.length} prototype files`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
|
|
122
|
+
main().catch((error) => {
|
|
123
|
+
console.error(error.message);
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { lstat, readdir, readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { extractPrototypeSources, materialize } from "./materialize-prototype.mjs";
|
|
6
|
+
|
|
7
|
+
const REQUIRED_HEADINGS = [
|
|
8
|
+
"## Project Evidence",
|
|
9
|
+
"## Style Decision",
|
|
10
|
+
"## Foundation Tokens",
|
|
11
|
+
"## Component and State Contract",
|
|
12
|
+
"## Interaction Contract",
|
|
13
|
+
"## Responsive Contract",
|
|
14
|
+
"## Prototype Sources",
|
|
15
|
+
"## Validation and Handoff",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
function frontmatterValue(header, key) {
|
|
19
|
+
const match = header.match(new RegExp(`^${key}:\\s*(.*?)\\s*$`, "m"));
|
|
20
|
+
if (!match) return undefined;
|
|
21
|
+
const value = match[1].trim();
|
|
22
|
+
if (value === "null") return null;
|
|
23
|
+
if (/^\d+$/.test(value)) return Number(value);
|
|
24
|
+
return value.replace(/^['"]|['"]$/g, "");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function frontmatterArray(header, key) {
|
|
28
|
+
const value = frontmatterValue(header, key);
|
|
29
|
+
if (typeof value !== "string" || !value.startsWith("[") || !value.endsWith("]")) return [];
|
|
30
|
+
return value.slice(1, -1).split(",").map((item) => item.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function isFile(path) {
|
|
34
|
+
try {
|
|
35
|
+
return (await stat(path)).isFile();
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code === "ENOENT") return false;
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function isSymlink(path) {
|
|
43
|
+
try {
|
|
44
|
+
return (await lstat(path)).isSymbolicLink();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code === "ENOENT") return false;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function validate(path, { allowDraft = false } = {}) {
|
|
52
|
+
const designPath = resolve(path);
|
|
53
|
+
if (await isSymlink(designPath)) throw new Error("design-system.md must not be a symlink");
|
|
54
|
+
const markdown = await readFile(designPath, "utf8");
|
|
55
|
+
const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
56
|
+
const errors = [];
|
|
57
|
+
if (!match) throw new Error("design-system.md requires YAML frontmatter");
|
|
58
|
+
const [, header, body] = match;
|
|
59
|
+
const meta = {
|
|
60
|
+
schemaVersion: frontmatterValue(header, "schema_version"),
|
|
61
|
+
artifact: frontmatterValue(header, "artifact"),
|
|
62
|
+
change: frontmatterValue(header, "change"),
|
|
63
|
+
designId: frontmatterValue(header, "design_id"),
|
|
64
|
+
status: frontmatterValue(header, "status"),
|
|
65
|
+
projectKind: frontmatterValue(header, "project_kind"),
|
|
66
|
+
platforms: frontmatterArray(header, "platforms"),
|
|
67
|
+
candidates: frontmatterArray(header, "style_candidates"),
|
|
68
|
+
selectedStyle: frontmatterValue(header, "selected_style"),
|
|
69
|
+
density: frontmatterValue(header, "density"),
|
|
70
|
+
colorMode: frontmatterValue(header, "color_mode"),
|
|
71
|
+
researchSnapshot: frontmatterValue(header, "research_snapshot"),
|
|
72
|
+
prototypeFiles: frontmatterArray(header, "prototype_files"),
|
|
73
|
+
updatedAt: frontmatterValue(header, "updated_at"),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const designRoot = dirname(designPath);
|
|
77
|
+
const expectedDesignId = basename(designRoot);
|
|
78
|
+
const expectedChange = basename(dirname(dirname(designRoot)));
|
|
79
|
+
if (meta.schemaVersion !== 2 || meta.artifact !== "ui-design-system") errors.push("artifact/schema_version must be ui-design-system/2");
|
|
80
|
+
if (!/^UI-\d{3,}$/.test(String(meta.designId ?? "")) || meta.designId !== expectedDesignId) errors.push("design_id must match the UI-NNN directory");
|
|
81
|
+
if (meta.change !== expectedChange) errors.push("change must match the owning change directory");
|
|
82
|
+
if (!new Set(["detecting", "selecting", "ready", "blocked"]).has(meta.status)) errors.push("invalid design status");
|
|
83
|
+
if (!new Set(["existing", "new"]).has(meta.projectKind)) errors.push("project_kind must be existing or new");
|
|
84
|
+
if (!meta.platforms.length || meta.platforms.some((value) => !new Set(["desktop", "web", "mobile"]).has(value))) errors.push("platforms must contain desktop, web, or mobile");
|
|
85
|
+
if (meta.candidates.length < 2 || meta.candidates.length > 4 || new Set(meta.candidates).size !== meta.candidates.length) errors.push("style_candidates must contain 2-4 unique values");
|
|
86
|
+
if (meta.researchSnapshot !== "49b5659") errors.push("research_snapshot must equal 49b5659");
|
|
87
|
+
for (const required of ["final/index.html", "final/styles.css", "final/app.js"]) {
|
|
88
|
+
if (!meta.prototypeFiles.includes(required)) errors.push(`prototype_files must include ${required}`);
|
|
89
|
+
}
|
|
90
|
+
if (typeof meta.updatedAt !== "string" || Number.isNaN(Date.parse(meta.updatedAt))) errors.push("updated_at must be ISO-8601");
|
|
91
|
+
for (const heading of REQUIRED_HEADINGS) if (!body.includes(heading)) errors.push(`missing '${heading}'`);
|
|
92
|
+
if (/(?:[A-Za-z]:[\\/]|\/(?:Users|home|tmp)\/)/.test(markdown)) errors.push("design package contains a machine-specific absolute path");
|
|
93
|
+
|
|
94
|
+
let sources;
|
|
95
|
+
try {
|
|
96
|
+
sources = extractPrototypeSources(markdown);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
errors.push(error.message);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (meta.status === "ready" && !allowDraft) {
|
|
102
|
+
if (!meta.selectedStyle || !meta.candidates.includes(meta.selectedStyle)) errors.push("ready design requires selected_style from style_candidates");
|
|
103
|
+
if (!new Set(["compact", "default", "comfortable", "touch"]).has(meta.density)) errors.push("ready design requires a density");
|
|
104
|
+
if (!new Set(["light", "dark", "system", "both"]).has(meta.colorMode)) errors.push("ready design requires a color_mode");
|
|
105
|
+
const comparisonIndex = join(designRoot, "comparison", "index.html");
|
|
106
|
+
const variantsRoot = join(designRoot, "comparison", "variants");
|
|
107
|
+
if (!(await isFile(comparisonIndex))) errors.push("ready design requires comparison/index.html");
|
|
108
|
+
if (await isSymlink(comparisonIndex)) errors.push("comparison/index.html must not be a symlink");
|
|
109
|
+
if (await isSymlink(join(designRoot, "comparison")) || await isSymlink(variantsRoot)) errors.push("comparison directories must not be symlinks");
|
|
110
|
+
let variants = [];
|
|
111
|
+
try {
|
|
112
|
+
variants = (await readdir(variantsRoot)).filter((entry) => entry.endsWith(".html"));
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error.code !== "ENOENT") throw error;
|
|
115
|
+
}
|
|
116
|
+
if (variants.length < 2 || variants.length > 4) errors.push("ready design requires 2-4 comparison variant HTML files");
|
|
117
|
+
for (const variant of variants) {
|
|
118
|
+
if (await isSymlink(join(variantsRoot, variant))) errors.push(`comparison variant must not be a symlink: ${variant}`);
|
|
119
|
+
}
|
|
120
|
+
if (await isSymlink(join(designRoot, "final"))) errors.push("final directory must not be a symlink");
|
|
121
|
+
if (sources) {
|
|
122
|
+
try {
|
|
123
|
+
await materialize(designPath, { check: true });
|
|
124
|
+
} catch (error) {
|
|
125
|
+
errors.push(error.message);
|
|
126
|
+
}
|
|
127
|
+
const html = sources.get("final/index.html") ?? "";
|
|
128
|
+
const css = sources.get("final/styles.css") ?? "";
|
|
129
|
+
const js = sources.get("final/app.js") ?? "";
|
|
130
|
+
if (!/href=["']styles\.css["']/.test(html) || !/src=["']app\.js["']/.test(html)) errors.push("final HTML must reference relative styles.css and app.js");
|
|
131
|
+
if (/<(?:script|link)[^>]+(?:src|href)=["']https?:/i.test(html)) errors.push("final HTML must not load remote scripts or styles");
|
|
132
|
+
if (!css.includes(":focus-visible")) errors.push("final CSS must define :focus-visible");
|
|
133
|
+
if (!css.includes("prefers-reduced-motion")) errors.push("final CSS must handle prefers-reduced-motion");
|
|
134
|
+
if (!js.includes("addEventListener")) errors.push("final JS must implement an explicit interaction");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (errors.length) throw new Error(errors.join("\n"));
|
|
139
|
+
return meta;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const args = process.argv.slice(2);
|
|
143
|
+
const allowDraft = args.includes("--allow-draft");
|
|
144
|
+
const paths = args.filter((arg) => arg !== "--allow-draft");
|
|
145
|
+
if (paths.length !== 1) {
|
|
146
|
+
console.error("usage: validate-design-package.mjs [--allow-draft] <design-system.md>");
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
} else {
|
|
149
|
+
validate(paths[0], { allowDraft })
|
|
150
|
+
.then((meta) => console.log(`valid ${meta.designId} (${meta.status})`))
|
|
151
|
+
.catch((error) => {
|
|
152
|
+
console.error(error.message);
|
|
153
|
+
process.exitCode = 1;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -18,7 +18,7 @@ SpecDev 将“理解、决定、规划、执行、验证、沉淀”拆成职责
|
|
|
18
18
|
↓
|
|
19
19
|
Triage 冻结为本地 Source
|
|
20
20
|
↓
|
|
21
|
-
Diagnose / Grill /
|
|
21
|
+
Diagnose / Grill / Wayfinder / Prototype / Code Review / Architecture Review
|
|
22
22
|
↓
|
|
23
23
|
Spec 外部行为、范围、验收合同与关键约束
|
|
24
24
|
↓
|
|
@@ -50,9 +50,12 @@ Archive 归档历史并将经验证知识提升为当前长期知识
|
|
|
50
50
|
- `<Path>{roots.state}/specdev/changes/{change}/investigation/</Path>`
|
|
51
51
|
- `<Path>{roots.state}/specdev/changes/{change}/evidence/</Path>`
|
|
52
52
|
- `<Path>{roots.state}/specdev/changes/{change}/reviews/</Path>`
|
|
53
|
-
- `<Path>{roots.state}/specdev/changes/{change}/prototypes
|
|
53
|
+
- `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/design-system.md</Path>`
|
|
54
|
+
- `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/comparison/</Path>`
|
|
55
|
+
- `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/final/</Path>`
|
|
54
56
|
- `<Path>{roots.state}/specdev/changes/{change}/questionnaires/</Path>`
|
|
55
|
-
|
|
57
|
+
|
|
58
|
+
`{design-id}` 由 P-prototype 在当前 change 内分配为最小未占用的 `UI-NNN`;设计系统文档是设计权威,comparison 与 final 是其可运行投影。
|
|
56
59
|
|
|
57
60
|
工件职责和冲突裁决位于 `<Path>{roots.workflows}/specdev/common/rules/artifact-contract.md</Path>`。
|
|
58
61
|
|
|
@@ -99,9 +102,10 @@ Archive 归档历史并将经验证知识提升为当前长期知识
|
|
|
99
102
|
- `<Path>{roots.state}/specdev/changes/{change}/architecture-review.html</Path>`
|
|
100
103
|
- `<Path>{roots.state}/specdev/changes/{change}/evidence/</Path>`
|
|
101
104
|
- `<Path>{roots.state}/specdev/changes/{change}/reviews/</Path>`
|
|
102
|
-
- `<Path>{roots.state}/specdev/changes/{change}/prototypes
|
|
105
|
+
- `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/design-system.md</Path>`
|
|
106
|
+
- `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/comparison/</Path>`
|
|
107
|
+
- `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/final/</Path>`
|
|
103
108
|
- `<Path>{roots.state}/specdev/changes/{change}/questionnaires/</Path>`
|
|
104
|
-
- `<Path>{roots.state}/specdev/changes/{change}/eli_index.md</Path>` 与 `<Path>{roots.state}/specdev/changes/{change}/<number>_<topic>.md</Path>`
|
|
105
109
|
|
|
106
110
|
## 全局治理原则
|
|
107
111
|
|
|
@@ -158,7 +162,7 @@ Change 从 active/blocked 转为 completed 时加载 `<Path>{roots.workflows}/sp
|
|
|
158
162
|
|
|
159
163
|
`active[].change` 必须唯一,且不得同时出现在 `archived`。开始 Work 时设置 `current_work`;暂停或可恢复阻塞时保留;成功完成时加入 `works_run` 并清空;取消时清空但不加入。逐次时间、结果和审计证据由 change 自有状态、Work 主产物、Evidence 或 LOG 承载,不写入全局索引。
|
|
160
164
|
|
|
161
|
-
`<Path>{roots.state}/specdev/config.json</Path>` 的 `execution.max_implementation_agents`、`max_integration_attempts` 和 planning
|
|
165
|
+
`<Path>{roots.state}/specdev/config.json</Path>` 的 `execution.max_implementation_agents`、`max_integration_attempts` 和 planning UI 设计候选字段均为可配置正整数;候选默认值与上限必须落在 2-4 且默认值不大于上限。仅 implementation subagent 受前者约束且不含 Lead,current workspace 仍保持单 writer 串行安全不变量;只读 review/research/test-observation agent 不设 SpecDev 数字上限。
|
|
162
166
|
|
|
163
167
|
`<Path>{roots.state}/specdev/changes/{change}/.status.json</Path>` 的 `worktrees` 保存 Ticket 级 `base_sha`、父分支、workspace/implementation/integration owner、workspace locator、implementation/source checkpoint、适用 candidate/result SHA、验证、E2E disposition 与生命周期状态。current 记录使用 `workspace_ref=current` 和 direct-parent;required 记录使用 source/parent-candidate。每个实现 Ticket 都有一条记录;父分支只有在对应策略的验证通过后推进。`removed` 是 required 集成后来源 branch/worktree 完成清理的终态,必须保留全部集成与 E2E 证据。
|
|
164
168
|
|
|
@@ -190,9 +194,8 @@ Change 从 active/blocked 转为 completed 时加载 `<Path>{roots.workflows}/sp
|
|
|
190
194
|
| 本地 change 完成且来源可关闭 | T-triage reconcile | A |
|
|
191
195
|
| 疑难 bug 或性能回归 | D-diagnose-bugs | S / T / I / R / W |
|
|
192
196
|
| 模糊但可通过决策访谈收敛 | G-grill-with-docs | P / S / T / W |
|
|
193
|
-
| 需要向刚上大一、没有专业背景的读者做 Markdown 与 ASCII 图解 | E-eli5 | 返回用户 / 继续当前 change |
|
|
194
197
|
| 路径超出单次上下文 | W-wayfinder | G / P / D / S / T |
|
|
195
|
-
|
|
|
198
|
+
| 需要检测项目 UI、选择设计方向并生成可运行设计包 | P-prototype | G / S / T / I |
|
|
196
199
|
| 固定点 diff、branch 或 PR review | C-code-review | completed / T / S / G |
|
|
197
200
|
| 外部行为已清楚 | S-spec | T-tickets |
|
|
198
201
|
| Ready Spec 需要垂直切片 | T-tickets | P-goal-plan / I |
|
|
@@ -209,13 +212,11 @@ Change 从 active/blocked 转为 completed 时加载 `<Path>{roots.workflows}/sp
|
|
|
209
212
|
- **A-archive-and-consolidate** — 归档与沉淀:校验本地完成与远程 reconcile 门,复用全局归档能力移动 completed change 并提升当前知识,或从代码访谈形成可归档知识 change。
|
|
210
213
|
- **C-code-review** — 代码审查:将 commit、branch、tag、merge-base 或 PR 解析为本地不可变固定点,执行隔离的标准轴与规范轴审查并持久化可恢复报告。
|
|
211
214
|
- **D-diagnose-bugs** — 诊断 Bug:先建立会在精确症状上变红的紧凑反馈回路,再通过最小化、排名假设和单变量探针确认根因,输出修复契约而不实施生产修复。
|
|
212
|
-
- **E-eli5** — 零基础新生解释:面向刚上大一、没有专业背景的读者解释一个主题;用 Markdown 和 ASCII 图解说明概念、数据与调用如何流动。
|
|
213
|
-
- **E-engineering-cognitive-mentor** — 工程认知导师:面向 Bug、项目源码、需求技术方案、架构设计与陌生技术领域的非执行型认知指导 Work;以证据、因果 Why、候选方案对比和逐轮澄清帮助用户形成可复述理解,并将完整问答轨迹持续持久化到当前 change。
|
|
214
215
|
- **G-grill-with-docs** — 设计访谈(带文档):以完整 frontier 逐轮推进设计树,直到每个决策分支都已关闭并获得用户共识,同时持续维护当前 change 的设计树、日志、领域上下文和架构决策。
|
|
215
216
|
- **I-implement** — 实现:基于 Ready Ticket 或获批小型 Spec 执行设计检查、TDD、动态派单、双轴审查、按 Goal Plan 选择的 current workspace 或 Ticket worktree 提交、直接父分支或候选合并验证和 Lead Evidence 回写。
|
|
216
217
|
- **I-init-setup** — 初始化设置:初始化 SpecDev 的语言、配置、全局状态、本地 change 追踪、领域知识布局、验证命令和并发治理。
|
|
217
218
|
- **P-goal-plan** — 目标规划:在跨 Ticket 协调复杂度需要时,以固定 Lead、动态派单、DAG/Gate 和候选合并门禁生成决策完备且可恢复的执行计划。
|
|
218
|
-
- **P-prototype** —
|
|
219
|
+
- **P-prototype** — UI 设计原型:检测现有项目的 UI 事实,按产品任务推荐并逐步选择设计风格,生成持久化设计系统文档、多风格 HTML 对照和可运行 HTML/CSS/JS 原型。
|
|
219
220
|
- **R-review-architecture** — 架构审查:从用户指定范围或 Git 热点扫描代码库的深化机会,以持久化可视化 HTML 呈现候选,并对用户选择的一个方案运行设计树访谈。
|
|
220
221
|
- **S-spec** — 编写 Spec:综合已知事实、设计决定、诊断与代码现状,产出以外部行为和验收合同为权威的 Ready Spec。
|
|
221
222
|
- **T-tickets** — 拆分 Tickets:将 Spec、计划或已确认对话拆成曳光弹式垂直切片;每个 Ticket 决策完备、可独立验证、适配单一上下文,并建立阻塞 DAG、路径所有权和执行就绪门禁。
|
|
@@ -38,7 +38,7 @@ Triage 是 SpecDev 唯一的远程摄入与关闭边界。开发期间,`<Path>
|
|
|
38
38
|
- 根因未知的 bug → `<Path>{roots.workflows}/specdev/D-diagnose-bugs/D-diagnose-bugs.md</Path>`;
|
|
39
39
|
- 产品或架构决定未锁定 → `<Path>{roots.workflows}/specdev/G-grill-with-docs/G-grill-with-docs.md</Path>`;
|
|
40
40
|
- 路径超出单次上下文 → `<Path>{roots.workflows}/specdev/W-wayfinder/W-wayfinder.md</Path>`;
|
|
41
|
-
-
|
|
41
|
+
- 需要检测项目 UI、比较风格候选并固化可运行设计包 → `<Path>{roots.workflows}/specdev/P-prototype/P-prototype.md</Path>`;
|
|
42
42
|
- 外部行为明确 → `<Path>{roots.workflows}/specdev/S-spec/S-spec.md</Path>`;
|
|
43
43
|
- 固定点 diff 或 PR 审查 → `<Path>{roots.workflows}/specdev/C-code-review/C-code-review.md</Path>`;
|
|
44
44
|
- 小型明确变更 → `<Path>{roots.workflows}/specdev/T-tickets/T-tickets.md</Path>` 或获批 Direct Spec。
|
|
@@ -47,7 +47,7 @@ Wayfinder 默认进行**规划**:每个 Ticket 解决一个决策,当地图
|
|
|
47
47
|
每个 Ticket 要么是 **HITL**,与一个代表自己发言的人类一起工作;要么是 **AFK**,由 Agent 独立驱动。HITL Ticket 只能通过实时交流解决,Agent 绝不代替人类一方发言。
|
|
48
48
|
|
|
49
49
|
- **Research(AFK)**:阅读文档、第三方 API 或知识库等资源,揭示某个决策等待的事实。调用 `<Path>{roots.workflows}/specdev/common/skills/research/SKILL.md</Path>`。当需要当前工作目录之外的知识时使用。
|
|
50
|
-
- **Prototype(HITL)**:调用 `<Path>{roots.workflows}/specdev/P-prototype/P-prototype.md</Path>`
|
|
50
|
+
- **Prototype(HITL)**:调用 `<Path>{roots.workflows}/specdev/P-prototype/P-prototype.md</Path>` 检测项目 UI、比较功能风格候选并逐步确认设计方向,把 `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/design-system.md</Path>` 与 comparison locator 链接为 solution comment 资产;`{design-id}` 使用 P 返回的 `UI-NNN`,P 不实现目的地。
|
|
51
51
|
- **Grilling(HITL)**:对话。调用 `<Path>{roots.workflows}/specdev/G-grill-with-docs/G-grill-with-docs.md</Path>` 的 grilling 与 domain-modeling 能力,但本会话只关闭当前 Wayfinder Ticket。
|
|
52
52
|
- **Task(HITL 或 AFK)**:在决策做出前必须完成的手动工作。它通过为决策解除阻塞赢得位置,不以交付目的地为目标。Agent 能独立驱动时使用 AFK,否则给人类精确清单。
|
|
53
53
|
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
- 分诊:`<Path>{roots.workflows}/specdev/common/schemas/triage.schema.json</Path>`
|
|
38
38
|
- 诊断:`<Path>{roots.workflows}/specdev/common/schemas/diagnosis.schema.json</Path>`
|
|
39
39
|
- 代码审查:`<Path>{roots.workflows}/specdev/common/schemas/code-review.schema.json</Path>`
|
|
40
|
-
-
|
|
40
|
+
- UI 设计包:`<Path>{roots.workflows}/specdev/P-prototype/design-package.schema.json</Path>`
|
|
41
41
|
|
|
42
42
|
## 工具与 Skill
|
|
43
43
|
|
|
44
44
|
- 包与 change 校验器:`<Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path>`
|
|
45
45
|
- 校验器说明:`<Path>{roots.workflows}/specdev/common/tools/README.md</Path>`
|
|
46
46
|
- 外部技术研究 Skill:`<Path>{roots.workflows}/specdev/common/skills/research/SKILL.md</Path>`
|
|
47
|
-
- Ticket
|
|
47
|
+
- Ticket worktree Skill:`<Path>{roots.workflows}/specdev/common/skills/dev-worktree/SKILL.md</Path>`;仅 required Ticket 使用 source → parent-candidate → parent 状态机,current Ticket 使用 current workspace/direct-parent
|
|
48
48
|
- 动态 Agent 交付合同 Skill:`<Path>{roots.workflows}/specdev/common/skills/subagent-delivery/SKILL.md</Path>`;P-goal-plan 建立 Lead 合同,I-implement 在执行期派单与验收
|
|
49
49
|
- 双轴代码审查 Skill:`<Path>{roots.workflows}/specdev/common/skills/code-review/SKILL.md</Path>`
|
|
50
50
|
|
|
@@ -19,14 +19,15 @@ SpecDev 通过分层工件避免同一决策被多个模型反复重做。每个
|
|
|
19
19
|
| Goal Plan | `<Path>{roots.state}/specdev/changes/{change}/goal-plan.md</Path>` | 跨 Ticket 调度、Gate、共享所有权、迁移顺序、集成和偏差治理 | 复制 Ticket 全文 |
|
|
20
20
|
| Evidence | `<Path>{roots.state}/specdev/changes/{change}/evidence/{ticket-id}.md</Path>` | 实际修改、命令、结果、验收映射、偏差、风险和提交引用 | 新的产品或架构决策 |
|
|
21
21
|
| 代码审查 | `<Path>{roots.state}/specdev/changes/{change}/reviews/CR-###.md</Path>` | 固定点、标准轴和规范轴 finding | 实施修复或合并两轴排名 |
|
|
22
|
-
|
|
|
23
|
-
| 零基础新生图解 | `<Path>{roots.state}/specdev/changes/{change}/eli_index.md</Path>` 与 `<Path>{roots.state}/specdev/changes/{change}/<number>_<topic>.md</Path>` | 面向刚上大一、没有专业背景读者的 Markdown 与 ASCII 图解;索引按序号持续追加 | 产品决定、架构决定或实现授权 |
|
|
22
|
+
| UI 设计包 | `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/design-system.md</Path>`、`<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/comparison/</Path>` 与 `<Path>{roots.state}/specdev/changes/{change}/prototypes/{design-id}/final/</Path>` | 项目 UI 证据、功能风格候选、逐层用户决定、设计 token、交互合同和可运行 HTML/CSS/JS 投影 | 生产 UI 实现或替用户确认高影响偏好 |
|
|
24
23
|
| Stakeholder 问卷 | `<Path>{roots.state}/specdev/changes/{change}/questionnaires/{slug}.md</Path>` | 第三方原始回答和恢复条件 | 未经转录确认的产品/架构决定 |
|
|
25
24
|
| Wayfinder 地图 | `<Path>{roots.state}/specdev/changes/{change}/wayfinder-map.md</Path>` | 目的地、说明、已关闭决策索引、战争迷雾和范围之外 | 开放 Ticket 正文或答案详情 |
|
|
26
25
|
| Wayfinder Ticket | `<Path>{roots.state}/specdev/changes/{change}/investigation/{investigation-id}.md</Path>` | 一个可精确陈述的问题、类型、阻塞和关闭状态 | 解决方案评论或交付目标 |
|
|
27
26
|
| Wayfinder solution comment | `<Path>{roots.state}/specdev/changes/{change}/investigation/comments/{investigation-id}/NN-solution.md</Path>` | Ticket 的答案、结果事实和资产指针 | 地图索引或产品实现 |
|
|
28
27
|
| 架构审查 | `<Path>{roots.state}/specdev/changes/{change}/architecture-review.md</Path>` 与 `<Path>{roots.state}/specdev/changes/{change}/architecture-review.html</Path>` | 深化候选、证据、可视化、选择和访谈状态 | 未经用户选择的执行契约 |
|
|
29
28
|
|
|
29
|
+
UI 设计包中的 `{design-id}` 由 P-prototype 分配为当前 change 内最小未占用的 `UI-NNN`;设计系统文档是唯一设计权威,comparison 与 final 不建立第二套规则。
|
|
30
|
+
|
|
30
31
|
Change CONTEXT/ADR 是 active change 内的执行权威,不是 workflow 级永久知识。G 和其他设计/执行 Works 只读 `<Path>{roots.state}/specdev/context/</Path>` 与 `<Path>{roots.state}/specdev/adr/</Path>`;只有 A 在 change 完成、实现证据验证、毕业评估和用户确认后才能写入永久 namespace。未毕业内容随归档 change 保留,不能从 change 工件消失。
|
|
31
32
|
|
|
32
33
|
## 2. 权威顺序
|
|
@@ -40,19 +40,19 @@
|
|
|
40
40
|
},
|
|
41
41
|
"planning": {
|
|
42
42
|
"type": "object",
|
|
43
|
-
"required": ["default_depth", "require_ready_gate", "require_evidence", "
|
|
43
|
+
"required": ["default_depth", "require_ready_gate", "require_evidence", "ui_design_default_candidates", "ui_design_max_candidates"],
|
|
44
44
|
"properties": {
|
|
45
45
|
"default_depth": {"enum": ["lite", "standard", "deep"]},
|
|
46
46
|
"require_ready_gate": {"type": "boolean"},
|
|
47
47
|
"require_evidence": {"type": "boolean"},
|
|
48
|
-
"
|
|
49
|
-
"
|
|
48
|
+
"ui_design_default_candidates": {"type": "integer", "minimum": 2, "maximum": 4},
|
|
49
|
+
"ui_design_max_candidates": {"type": "integer", "minimum": 2, "maximum": 4}
|
|
50
50
|
},
|
|
51
51
|
"additionalProperties": true
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"allOf": [{
|
|
55
|
-
"$comment": "
|
|
55
|
+
"$comment": "ui_design_default_candidates <= ui_design_max_candidates is enforced by validate-specdev.mjs because JSON Schema cannot compare sibling numeric values."
|
|
56
56
|
}],
|
|
57
57
|
"additionalProperties": false
|
|
58
58
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dev-worktree
|
|
3
|
-
description: 管理 SpecDev required Ticket
|
|
3
|
+
description: 管理 SpecDev required Ticket 的 Git worktree;创建隔离来源 workspace、固定实现 commit,并由 Lead 在 parent-candidate checkout 验证后推进父分支。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Dev Worktree
|
|
7
7
|
|
|
8
|
-
本 Skill 由 T-tickets
|
|
8
|
+
本 Skill 由 T-tickets、P-goal-plan 和 I-implement 复用。仅在 Goal Plan 选择 `required` 时使用完整 source → candidate → parent 状态机;`current` Ticket 不调用本 Skill。
|
|
9
9
|
|
|
10
10
|
## 输入
|
|
11
11
|
|
|
12
12
|
- `operation=create | restore | finalize | remove`;
|
|
13
|
-
- `purpose=ticket
|
|
13
|
+
- `purpose=ticket`;
|
|
14
14
|
- repository、父分支、`base_sha`、branch、portable workspace locator;
|
|
15
15
|
- workspace、implementation 和 integration owner;
|
|
16
16
|
- 允许动作、路径合同、验证合同、调用方状态记录位置。
|
|
@@ -39,7 +39,7 @@ implementation owner 只在来源 worktree 修改授权项目路径,运行 Tic
|
|
|
39
39
|
|
|
40
40
|
## 4. 移除
|
|
41
41
|
|
|
42
|
-
`operation=remove` 先验证 Ticket 已 `integrated
|
|
42
|
+
`operation=remove` 先验证 Ticket 已 `integrated`、目标 worktree clean、checkpoint 可恢复且删除目标精确。只有明确 cleanup 授权时删除来源 branch/worktree;强制删除需要单独确认。删除后重读 `git worktree list` 与 refs,并只把调用方生命周期状态更新为 `removed`;`base_sha`、source checkpoint、candidate/result、验证、E2E 与 Evidence 字段必须原样保留。
|
|
43
43
|
|
|
44
44
|
**完成标准**:只删除精确授权目标;失败保留现场与恢复命令。
|
|
45
45
|
|
|
@@ -8,14 +8,12 @@
|
|
|
8
8
|
- `specdev-worktree/` 已由 Speculo init 加入项目 `.gitignore`;
|
|
9
9
|
- 目标 branch/worktree 不覆盖现有用户 workspace,路径合同无冲突。
|
|
10
10
|
|
|
11
|
-
Prototype 只要求调用方已记录本次临时 branch/worktree 授权、问题、owner、locator 和清理策略;它不写 Ticket worktree 状态。
|
|
12
|
-
|
|
13
11
|
## 创建 Ticket 来源 worktree
|
|
14
12
|
|
|
15
13
|
1. 重读父分支 HEAD、工作树、现有 worktrees 与 refs;父 HEAD 与计划基线不一致时由 Lead决定更新 `base_sha` 或阻塞;
|
|
16
14
|
2. 固定 branch `speculo/<change>/<ticket-id>` 与 locator `specdev-worktree/<ticket-id>`;
|
|
17
15
|
3. 确认目标 branch/path 不存在,或其实际记录精确匹配当前 Ticket;
|
|
18
|
-
4. 从 `base_sha` 创建 Git worktree,不复用其他 Ticket
|
|
16
|
+
4. 从 `base_sha` 创建 Git worktree,不复用其他 Ticket 目录;
|
|
19
17
|
5. 在来源 worktree 读取项目 Agent 指令、依赖、构建与路径合同;
|
|
20
18
|
6. 安装实际需要的依赖,运行最小非 E2E 基线;
|
|
21
19
|
7. Lead 写入 `<Path>{roots.state}/specdev/changes/{change}/.status.json</Path>`,状态为 `active`。
|