superdev-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +21 -0
- package/.codex-plugin/plugin.json +27 -0
- package/CODE_OF_CONDUCT.md +109 -0
- package/CONTRIBUTING.md +205 -0
- package/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +1051 -0
- package/SECURITY.md +135 -0
- package/THIRD-PARTY-NOTICES.md +253 -0
- package/hooks/hooks.json +61 -0
- package/hooks/run.mjs +88 -0
- package/package.json +65 -0
- package/references/confidentiality.md +48 -0
- package/references/evidence-and-risk.md +73 -0
- package/references/operating-model.md +105 -0
- package/references/platform-capabilities.md +51 -0
- package/references/project-record.md +91 -0
- package/references/provider-contracts.md +102 -0
- package/scripts/doctor/doctor.mjs +539 -0
- package/scripts/privacy/scan-history.mjs +163 -0
- package/scripts/privacy/scan.mjs +368 -0
- package/scripts/validate/README.md +94 -0
- package/scripts/validate/common.mjs +149 -0
- package/scripts/validate/data-model.mjs +225 -0
- package/scripts/validate/dependencies.mjs +69 -0
- package/scripts/validate/docs-templates.mjs +99 -0
- package/scripts/validate/footprint.mjs +78 -0
- package/scripts/validate/imports.mjs +61 -0
- package/scripts/validate/manifests.mjs +173 -0
- package/scripts/validate/markdown.mjs +123 -0
- package/scripts/validate/migrations.mjs +187 -0
- package/scripts/validate/no-tests.mjs +136 -0
- package/scripts/validate/privacy.mjs +115 -0
- package/scripts/validate/record-links.mjs +127 -0
- package/scripts/validate/skill-commands.mjs +190 -0
- package/scripts/validate/skills.mjs +112 -0
- package/scripts/validate/specification.mjs +88 -0
- package/scripts/validate/style.mjs +78 -0
- package/scripts/validate/validate-all.mjs +175 -0
- package/skills/debug/SKILL.md +68 -0
- package/skills/decision/SKILL.md +99 -0
- package/skills/docs/SKILL.md +86 -0
- package/skills/docs/assets/fragments/api/events.md +14 -0
- package/skills/docs/assets/fragments/api/graphql.md +14 -0
- package/skills/docs/assets/fragments/api/local-only.md +13 -0
- package/skills/docs/assets/fragments/api/rest.md +13 -0
- package/skills/docs/assets/fragments/api/rpc.md +13 -0
- package/skills/docs/assets/fragments/async/event-bus.md +13 -0
- package/skills/docs/assets/fragments/async/none.md +11 -0
- package/skills/docs/assets/fragments/async/platform-jobs.md +13 -0
- package/skills/docs/assets/fragments/async/queue.md +13 -0
- package/skills/docs/assets/fragments/async/scheduler.md +13 -0
- package/skills/docs/assets/fragments/auth/detected-provider.md +15 -0
- package/skills/docs/assets/fragments/auth/neutral-base.md +14 -0
- package/skills/docs/assets/fragments/data/document.md +13 -0
- package/skills/docs/assets/fragments/data/external-saas.md +13 -0
- package/skills/docs/assets/fragments/data/key-value.md +13 -0
- package/skills/docs/assets/fragments/data/none.md +12 -0
- package/skills/docs/assets/fragments/data/sql-orm.md +13 -0
- package/skills/docs/assets/fragments/data/sql-plain.md +13 -0
- package/skills/docs/assets/fragments/env/conventional.md +13 -0
- package/skills/docs/assets/fragments/env/envx.md +13 -0
- package/skills/docs/assets/fragments/env/managed-platform.md +13 -0
- package/skills/docs/assets/fragments/env/none.md +11 -0
- package/skills/docs/assets/fragments/ui/api-only.md +13 -0
- package/skills/docs/assets/fragments/ui/cli.md +14 -0
- package/skills/docs/assets/fragments/ui/desktop.md +14 -0
- package/skills/docs/assets/fragments/ui/mobile.md +14 -0
- package/skills/docs/assets/fragments/ui/web.md +14 -0
- package/skills/docs/assets/templates/adr.md +62 -0
- package/skills/docs/assets/templates/api.md +30 -0
- package/skills/docs/assets/templates/architecture.md +52 -0
- package/skills/docs/assets/templates/change-impact-drift-report.md +29 -0
- package/skills/docs/assets/templates/compliance.md +29 -0
- package/skills/docs/assets/templates/data-schema.md +43 -0
- package/skills/docs/assets/templates/feature.md +43 -0
- package/skills/docs/assets/templates/foundations.md +55 -0
- package/skills/docs/assets/templates/jobs-webhooks.md +36 -0
- package/skills/docs/assets/templates/module-inventory.md +19 -0
- package/skills/docs/assets/templates/module.md +50 -0
- package/skills/docs/assets/templates/nfr.md +22 -0
- package/skills/docs/assets/templates/observability.md +28 -0
- package/skills/docs/assets/templates/pages-ui-actions.md +47 -0
- package/skills/docs/assets/templates/project-summary.md +44 -0
- package/skills/docs/assets/templates/roles-permissions.md +26 -0
- package/skills/docs/assets/templates/test-plan.md +23 -0
- package/skills/docs/assets/templates/workflow-state-machine.md +43 -0
- package/skills/docs/references/adr-authoring.md +24 -0
- package/skills/docs/references/apis-and-data.md +23 -0
- package/skills/docs/references/capability-fragments.md +25 -0
- package/skills/docs/references/change-tracking.md +62 -0
- package/skills/docs/references/diagrams.md +31 -0
- package/skills/docs/references/discovery.md +59 -0
- package/skills/docs/references/edge-cases.md +45 -0
- package/skills/docs/references/foundations-modules.md +18 -0
- package/skills/docs/references/ingestion.md +52 -0
- package/skills/docs/references/initialize-adopt.md +25 -0
- package/skills/docs/references/module-decomposition.md +38 -0
- package/skills/docs/references/profiles.md +60 -0
- package/skills/docs/references/quality-attributes.md +27 -0
- package/skills/docs/references/reverse-engineer.md +21 -0
- package/skills/docs/references/spec-depths.md +31 -0
- package/skills/docs/references/summarize.md +18 -0
- package/skills/docs/references/surfaces-and-actions.md +24 -0
- package/skills/docs/references/validation.md +47 -0
- package/skills/docs/references/workflows-and-jobs.md +25 -0
- package/skills/docs/scripts/ingest.mjs +984 -0
- package/skills/docs/scripts/profile-detect.mjs +299 -0
- package/skills/docs/scripts/screen.mjs +96 -0
- package/skills/docs/scripts/template-lint.mjs +143 -0
- package/skills/docs/scripts/validate-docs.mjs +363 -0
- package/skills/doctor/SKILL.md +167 -0
- package/skills/feature/SKILL.md +132 -0
- package/skills/init/SKILL.md +137 -0
- package/skills/project/SKILL.md +261 -0
- package/skills/project/references/commands.md +213 -0
- package/skills/resume/SKILL.md +79 -0
- package/skills/review/SKILL.md +91 -0
- package/skills/status/SKILL.md +85 -0
- package/skills/task/SKILL.md +183 -0
- package/src/cli/product-map.mjs +468 -0
- package/src/cli/render.mjs +544 -0
- package/src/cli.mjs +2774 -0
- package/src/cloud/crypto.mjs +118 -0
- package/src/cloud/merge.mjs +186 -0
- package/src/cloud/policy.mjs +115 -0
- package/src/cloud/sync.mjs +512 -0
- package/src/cloud/transport.mjs +116 -0
- package/src/db/connect.mjs +189 -0
- package/src/db/maintenance.mjs +419 -0
- package/src/db/migrate.mjs +188 -0
- package/src/db/migrations/001_initial.sql +1024 -0
- package/src/db/migrations/002_docs_coverage.sql +126 -0
- package/src/db/migrations/003_memory_retrieval_and_integrity.sql +168 -0
- package/src/db/migrations/004_task_categories.sql +59 -0
- package/src/db/migrations/005_executable_evidence.sql +32 -0
- package/src/db/migrations/006_module_and_feature_boundaries.sql +27 -0
- package/src/db/migrations/007_drop_redundant_module_users.sql +13 -0
- package/src/db/migrations/008_changes_assumptions_test_plans_api_services.sql +166 -0
- package/src/db/migrations/009_retired_documents.sql +54 -0
- package/src/db/migrations/010_test_plan_evidence.sql +20 -0
- package/src/db/migrations/011_criterion_waiver.sql +13 -0
- package/src/db/migrations/012_synchronization.sql +56 -0
- package/src/db/store.mjs +327 -0
- package/src/decisions/record.mjs +167 -0
- package/src/docs/proposals.mjs +871 -0
- package/src/docs/render.mjs +1424 -0
- package/src/docs/templates.mjs +1281 -0
- package/src/features/acceptance.mjs +315 -0
- package/src/features/specify.mjs +248 -0
- package/src/init/discovery.mjs +901 -0
- package/src/init/index.mjs +784 -0
- package/src/init/questions.mjs +562 -0
- package/src/memory/benchmark.mjs +171 -0
- package/src/memory/capture.mjs +135 -0
- package/src/memory/consolidate.mjs +255 -0
- package/src/memory/index.mjs +810 -0
- package/src/model/ids.mjs +185 -0
- package/src/model/screening.mjs +150 -0
- package/src/model/toolkit.mjs +258 -0
- package/src/model/vocabulary.mjs +190 -0
- package/src/product/assumptions.mjs +120 -0
- package/src/product/changes.mjs +180 -0
- package/src/product/test-plans.mjs +183 -0
- package/src/progress/index.mjs +1364 -0
- package/src/runtime/harness.mjs +264 -0
- package/src/runtime/hooks.mjs +1021 -0
- package/src/runtime/identity.mjs +305 -0
- package/src/runtime/resume.mjs +343 -0
- package/src/runtime/session.mjs +679 -0
- package/src/runtime/version.mjs +315 -0
- package/src/service/assets/control-center.html +206 -0
- package/src/service/assets/control-center.manifest.json +7 -0
- package/src/service/manage.mjs +542 -0
- package/src/service/mutations.mjs +860 -0
- package/src/service/read-model.mjs +1557 -0
- package/src/service/server.mjs +783 -0
- package/src/tasks/categories.mjs +154 -0
- package/src/tasks/derive.mjs +977 -0
- package/src/tasks/lifecycle.mjs +830 -0
- package/src/verify/index.mjs +236 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Profile-aware documentation validator. Design guarantees:
|
|
4
|
+
* - flags parse independently of positional roots; --json is never a path
|
|
5
|
+
* - read-only; writes only to an explicit --out path
|
|
6
|
+
* - prohibitions are declarations; only actual usage in executable surfaces
|
|
7
|
+
* (package scripts, Makefiles, CI) is flagged - never prose. Scope: only
|
|
8
|
+
* backticked commands ("never use/run `X`") are machine-checkable
|
|
9
|
+
* - template folders and adapter-excluded globs are exempt from checks
|
|
10
|
+
* - historical/superseded documents (status marker within the first ~30
|
|
11
|
+
* lines) are exempt from active-contract checks
|
|
12
|
+
* - workspace members are scanned, not only the root package
|
|
13
|
+
* - profile-specific requirements; never forces talks-v1 on other profiles
|
|
14
|
+
* - findings carry stable rule ids + evidence paths; no file content is echoed
|
|
15
|
+
* (secret-safe by construction)
|
|
16
|
+
*
|
|
17
|
+
* Finding codes:
|
|
18
|
+
* DV-STRUCT-001 P0 required artifact missing (per profile)
|
|
19
|
+
* DV-STRUCT-002 P1 unregistered (orphan) module folder
|
|
20
|
+
* DV-TESTPLAN-001 P1 module missing its test plan (profiles that require one)
|
|
21
|
+
* DV-LINK-001 P0 broken relative link in current-state doc
|
|
22
|
+
* DV-PROHIB-001 P0 declared-prohibited command actually used in an executable surface
|
|
23
|
+
* DV-SUNSET-001 P2 expired "remove after YYYY-MM-DD" marker
|
|
24
|
+
* DV-PROFILE-001 P0 adapter declares an unrecognized profile (fail closed)
|
|
25
|
+
* DV-PATH-001 P0 adapter path is absolute (must be project-relative)
|
|
26
|
+
* DV-PATH-002 P0 adapter path traverses outside the project root
|
|
27
|
+
* DV-PATH-003 P0 adapter path escapes the project root through a symlink
|
|
28
|
+
* DV-WS-001 P1 unsupported or unsafe workspace pattern (inspection incomplete)
|
|
29
|
+
*
|
|
30
|
+
* Adapter data (talks/project.yaml) is untrusted repository input: declared
|
|
31
|
+
* paths are validated (absolute/traversal/symlink-escape rejected, field name
|
|
32
|
+
* reported without echoing the value) BEFORE any read they would direct.
|
|
33
|
+
*
|
|
34
|
+
* Exit codes: 0 clean, 1 findings, 2 usage error.
|
|
35
|
+
*/
|
|
36
|
+
import { parseArgs } from "node:util";
|
|
37
|
+
import fs from "node:fs";
|
|
38
|
+
import path from "node:path";
|
|
39
|
+
import { pathToFileURL } from "node:url";
|
|
40
|
+
import { detectProfile, readYamlSubset, dependencyInventory } from "./profile-detect.mjs";
|
|
41
|
+
|
|
42
|
+
const USAGE = `Usage: node validate-docs.mjs [--root <path>] [--profile <id>] [--baseline <file>] [--json] [--out <file>] [--help]
|
|
43
|
+
--root <path> project root to validate (default: current directory)
|
|
44
|
+
--profile <id> override profile (talks-v1 | module-docs | legacy-flat-docs | custom)
|
|
45
|
+
--baseline <file> suppress known findings: JSON array of "RULE:path:detail" (or "RULE:path") fingerprints
|
|
46
|
+
--json machine-readable JSON on stdout
|
|
47
|
+
--out <file> also write the JSON report to this exact path
|
|
48
|
+
--help show this help`;
|
|
49
|
+
|
|
50
|
+
const DEFAULT_EXCLUDES = ["_templates", "node_modules", ".git", "vendor", "dist", "build"];
|
|
51
|
+
export const KNOWN_PROFILES = new Set(["talks-v1", "module-docs", "legacy-flat-docs", "custom", "none"]);
|
|
52
|
+
|
|
53
|
+
/** Root-confinement check for an adapter-declared relative path. Returns a
|
|
54
|
+
* finding (field name only - the value is never echoed) or null when safe. */
|
|
55
|
+
export function checkAdapterPath(root, field, value) {
|
|
56
|
+
const v = String(value);
|
|
57
|
+
if (path.isAbsolute(v) || /^[A-Za-z]:[\\/]/.test(v))
|
|
58
|
+
return { ruleId: "DV-PATH-001", severity: "P0", file: "talks/project.yaml", detail: `absolute path in docs.${field}` };
|
|
59
|
+
if (v.split(/[\\/]/).includes(".."))
|
|
60
|
+
return { ruleId: "DV-PATH-002", severity: "P0", file: "talks/project.yaml", detail: `path traversal in docs.${field}` };
|
|
61
|
+
const rootReal = fs.realpathSync(root);
|
|
62
|
+
const resolved = path.resolve(rootReal, v);
|
|
63
|
+
if (resolved !== rootReal && !resolved.startsWith(rootReal + path.sep))
|
|
64
|
+
return { ruleId: "DV-PATH-002", severity: "P0", file: "talks/project.yaml", detail: `path resolves outside root in docs.${field}` };
|
|
65
|
+
if (fs.existsSync(resolved)) {
|
|
66
|
+
const real = fs.realpathSync(resolved);
|
|
67
|
+
if (real !== rootReal && !real.startsWith(rootReal + path.sep))
|
|
68
|
+
return { ruleId: "DV-PATH-003", severity: "P0", file: "talks/project.yaml", detail: `symlink escape in docs.${field}` };
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Validate every adapter-controlled path field before any read they direct. */
|
|
74
|
+
export function checkAdapterPaths(root, adapterDocs) {
|
|
75
|
+
const findings = [];
|
|
76
|
+
const single = { canonicalRoot: adapterDocs.canonicalRoot, modulesRoot: adapterDocs.modulesRoot, ownership: adapterDocs.ownership };
|
|
77
|
+
for (const [field, value] of Object.entries(single)) {
|
|
78
|
+
if (typeof value !== "string") continue;
|
|
79
|
+
const f = checkAdapterPath(root, field, value);
|
|
80
|
+
if (f) findings.push(f);
|
|
81
|
+
}
|
|
82
|
+
for (const [field, list] of [["required", adapterDocs.required], ["excluded", adapterDocs.excluded]]) {
|
|
83
|
+
if (!Array.isArray(list)) continue;
|
|
84
|
+
list.forEach((value, i) => {
|
|
85
|
+
const bare = String(value).replace(/[*]/g, "x"); // confine the shape; globs share path rules
|
|
86
|
+
const f = checkAdapterPath(root, `${field}[${i}]`, bare);
|
|
87
|
+
if (f) findings.push(f);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return findings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** relPosix uses "/" on every platform. Multi-segment excludes match on path
|
|
94
|
+
* boundaries; single-name excludes match any path segment. Empty entries never match. */
|
|
95
|
+
export function isExcluded(relPosix, excludes) {
|
|
96
|
+
return excludes.some((ex) => {
|
|
97
|
+
if (!ex) return false;
|
|
98
|
+
if (ex.includes("/")) return relPosix === ex || relPosix.startsWith(ex + "/");
|
|
99
|
+
return relPosix.split("/").includes(ex);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function walkMd(dir, root, excludes, out = []) {
|
|
104
|
+
if (!fs.existsSync(dir)) return out;
|
|
105
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
106
|
+
if (entry.name.startsWith(".")) continue;
|
|
107
|
+
const full = path.join(dir, entry.name);
|
|
108
|
+
const relPosix = path.relative(root, full).split(path.sep).join("/");
|
|
109
|
+
if (isExcluded(relPosix, excludes)) continue;
|
|
110
|
+
if (entry.isDirectory()) walkMd(full, root, excludes, out);
|
|
111
|
+
else if (entry.name.endsWith(".md")) out.push(relPosix);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Historical detection window: the first 30 lines (accommodates frontmatter). */
|
|
117
|
+
export function isHistorical(text, markers) {
|
|
118
|
+
const head = text.split("\n").slice(0, 30).join("\n");
|
|
119
|
+
if (/^\s*-?\s*\**Status:?\**\s*(Superseded|Deprecated|Historical|Archived)/im.test(head)) return true;
|
|
120
|
+
return markers.some((m) => head.includes(m));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Prohibition declarations: "never use/run `X`" in prose. Scope: only
|
|
124
|
+
* backticked commands are machine-checkable; unbackticked prose prohibitions
|
|
125
|
+
* are deliberately out of scope (stated in references/validation.md). */
|
|
126
|
+
export function collectProhibitions(text) {
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const m of text.matchAll(/never\s+(?:use|run)\s+`([^`\n]+)`/gi)) out.push(m[1].trim());
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Executable surfaces where prohibited-command *usage* counts. */
|
|
133
|
+
export function collectExecutableUsage(root, inventory) {
|
|
134
|
+
const usage = [];
|
|
135
|
+
const { packages } = inventory ?? dependencyInventory(root);
|
|
136
|
+
for (const pkg of packages) {
|
|
137
|
+
for (const [name, cmd] of Object.entries(pkg.scripts)) {
|
|
138
|
+
usage.push({ where: `${pkg.path}#scripts.${name}`, text: String(cmd) });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const candidate of ["Makefile", "makefile"]) {
|
|
142
|
+
const p = path.join(root, candidate);
|
|
143
|
+
if (fs.existsSync(p)) usage.push({ where: candidate, text: fs.readFileSync(p, "utf8") });
|
|
144
|
+
}
|
|
145
|
+
const ciDirs = [path.join(root, ".github", "workflows"), path.join(root, ".ci")];
|
|
146
|
+
for (const dir of ciDirs) {
|
|
147
|
+
if (!fs.existsSync(dir)) continue;
|
|
148
|
+
for (const f of fs.readdirSync(dir)) {
|
|
149
|
+
if (/\.(ya?ml|sh)$/.test(f)) usage.push({ where: path.relative(root, path.join(dir, f)), text: fs.readFileSync(path.join(dir, f), "utf8") });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return usage;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function checkLinks(root, relFile, text) {
|
|
156
|
+
const findings = [];
|
|
157
|
+
const dir = path.dirname(path.join(root, relFile));
|
|
158
|
+
for (const m of text.matchAll(/\[[^\]]*\]\(([^)\s]+)\)/g)) {
|
|
159
|
+
const target = m[1];
|
|
160
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(target)) continue;
|
|
161
|
+
const clean = target.split("#")[0];
|
|
162
|
+
if (!clean) continue;
|
|
163
|
+
let decoded = clean;
|
|
164
|
+
try {
|
|
165
|
+
decoded = decodeURIComponent(clean);
|
|
166
|
+
} catch {
|
|
167
|
+
// literal % in the target - check the raw string instead of crashing
|
|
168
|
+
}
|
|
169
|
+
if (!fs.existsSync(path.resolve(dir, decoded)))
|
|
170
|
+
findings.push({ ruleId: "DV-LINK-001", severity: "P0", file: relFile, detail: `broken link: ${clean}` });
|
|
171
|
+
}
|
|
172
|
+
return findings;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function checkSunset(relFile, text, today) {
|
|
176
|
+
const findings = [];
|
|
177
|
+
for (const m of text.matchAll(/remove after (\d{4}-\d{2}-\d{2})/gi)) {
|
|
178
|
+
if (m[1] < today) findings.push({ ruleId: "DV-SUNSET-001", severity: "P2", file: relFile, detail: `expired sunset marker (${m[1]})` });
|
|
179
|
+
}
|
|
180
|
+
return findings;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Adapter exclude globs reduce to the matcher's two shapes: "**\/name/**" →
|
|
184
|
+
* segment exclude "name"; "dir/sub/**" → boundary-prefix exclude "dir/sub".
|
|
185
|
+
* Entries that reduce to nothing are dropped - an empty exclude must never match. */
|
|
186
|
+
export function normalizeExclude(glob) {
|
|
187
|
+
let e = String(glob).replace(/^\.\//, "");
|
|
188
|
+
if (e.startsWith("**/")) e = e.slice(3).replace(/\/?\*\*.*$/, "").replace(/\/.*$/, "");
|
|
189
|
+
else e = e.replace(/\/?\*\*.*$/, "");
|
|
190
|
+
return e.replace(/\/$/, "");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function profileConfig(profile, adapter, detected) {
|
|
194
|
+
const cfg = {
|
|
195
|
+
excludes: [...DEFAULT_EXCLUDES],
|
|
196
|
+
historicalMarkers: [],
|
|
197
|
+
required: [],
|
|
198
|
+
modulesRoot: null,
|
|
199
|
+
docsRoot: null,
|
|
200
|
+
requireModuleTestPlan: false,
|
|
201
|
+
};
|
|
202
|
+
const a = adapter?.docs ?? {};
|
|
203
|
+
if (Array.isArray(a.excluded)) cfg.excludes.push(...a.excluded.map(normalizeExclude).filter(Boolean));
|
|
204
|
+
if (Array.isArray(a.historicalMarkers)) cfg.historicalMarkers.push(...a.historicalMarkers);
|
|
205
|
+
if (Array.isArray(a.required)) cfg.required.push(...a.required);
|
|
206
|
+
cfg.docsRoot = a.canonicalRoot ?? detected?.docsRoot ?? null;
|
|
207
|
+
cfg.modulesRoot = a.modulesRoot ?? null;
|
|
208
|
+
|
|
209
|
+
if (profile === "talks-v1") {
|
|
210
|
+
cfg.docsRoot ??= "talks";
|
|
211
|
+
cfg.required.push("talks/project.yaml", "talks/state/schema-version.json");
|
|
212
|
+
cfg.modulesRoot ??= "talks/project/modules";
|
|
213
|
+
cfg.requireModuleTestPlan = true;
|
|
214
|
+
} else if (profile === "module-docs") {
|
|
215
|
+
if (!cfg.modulesRoot && cfg.docsRoot) cfg.modulesRoot = `${cfg.docsRoot}/modules`;
|
|
216
|
+
cfg.requireModuleTestPlan = true;
|
|
217
|
+
}
|
|
218
|
+
// legacy-flat-docs and custom: only adapter-declared requirements - never talks-v1's.
|
|
219
|
+
return cfg;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function validateDocs(root, { profile: profileOverride, baselinePath } = {}) {
|
|
223
|
+
if (profileOverride && !KNOWN_PROFILES.has(profileOverride))
|
|
224
|
+
throw new RangeError(`unknown profile: ${profileOverride} (known: ${[...KNOWN_PROFILES].join(", ")})`);
|
|
225
|
+
const detected = detectProfile(root);
|
|
226
|
+
const adapter = detected.adapter ?? null;
|
|
227
|
+
|
|
228
|
+
// Fail closed BEFORE any adapter-directed read or profile interpretation.
|
|
229
|
+
if (adapter?.docs) {
|
|
230
|
+
const early = [];
|
|
231
|
+
if (adapter.docs.profile && !KNOWN_PROFILES.has(adapter.docs.profile))
|
|
232
|
+
early.push({ ruleId: "DV-PROFILE-001", severity: "P0", file: "talks/project.yaml", detail: "unrecognized docs.profile (fail closed; no checks were run)" });
|
|
233
|
+
early.push(...checkAdapterPaths(root, adapter.docs));
|
|
234
|
+
if (early.length) {
|
|
235
|
+
const counts = { P0: early.filter((f) => f.severity === "P0").length, P1: early.filter((f) => f.severity === "P1").length, P2: 0 };
|
|
236
|
+
return { version: 1, profile: "rejected", profileSource: "adapter", root: path.resolve(root), counts, suppressed: 0, findings: early };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const profile = profileOverride ?? detected.profile;
|
|
241
|
+
const cfg = profileConfig(profile, adapter, detected);
|
|
242
|
+
const findings = [];
|
|
243
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
244
|
+
|
|
245
|
+
for (const req of cfg.required) {
|
|
246
|
+
if (!fs.existsSync(path.join(root, req)))
|
|
247
|
+
findings.push({ ruleId: "DV-STRUCT-001", severity: "P0", file: req, detail: "required artifact missing" });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (cfg.modulesRoot && fs.existsSync(path.join(root, cfg.modulesRoot))) {
|
|
251
|
+
for (const entry of fs.readdirSync(path.join(root, cfg.modulesRoot), { withFileTypes: true })) {
|
|
252
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
|
|
253
|
+
const moduleRel = path.join(cfg.modulesRoot, entry.name);
|
|
254
|
+
if (!fs.existsSync(path.join(root, moduleRel, "module.md"))) {
|
|
255
|
+
findings.push({ ruleId: "DV-STRUCT-002", severity: "P1", file: moduleRel, detail: "unregistered (orphan) module folder - no module.md" });
|
|
256
|
+
} else if (cfg.requireModuleTestPlan && !fs.existsSync(path.join(root, moduleRel, "test-plan.md"))) {
|
|
257
|
+
findings.push({ ruleId: "DV-TESTPLAN-001", severity: "P1", file: moduleRel, detail: "module missing test-plan.md" });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const inventory = dependencyInventory(root);
|
|
263
|
+
for (const u of inventory.unsupported) {
|
|
264
|
+
findings.push({ ruleId: "DV-WS-001", severity: "P1", file: "package.json", detail: `workspace inspection incomplete: ${u.reason} (${u.pattern})` });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const scanRoot = cfg.docsRoot && fs.existsSync(path.join(root, cfg.docsRoot)) ? path.join(root, cfg.docsRoot) : root;
|
|
268
|
+
const mdFiles = walkMd(scanRoot, root, cfg.excludes);
|
|
269
|
+
const prohibitions = new Set();
|
|
270
|
+
for (const rel of mdFiles) {
|
|
271
|
+
const text = fs.readFileSync(path.join(root, rel), "utf8");
|
|
272
|
+
const historical = isHistorical(text, cfg.historicalMarkers);
|
|
273
|
+
for (const p of collectProhibitions(text)) if (!historical) prohibitions.add(p);
|
|
274
|
+
if (historical) continue; // historical docs are exempt from active-contract checks
|
|
275
|
+
findings.push(...checkLinks(root, rel, text));
|
|
276
|
+
findings.push(...checkSunset(rel, text, today));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (prohibitions.size) {
|
|
280
|
+
const usage = collectExecutableUsage(root, inventory);
|
|
281
|
+
for (const prohibited of prohibitions) {
|
|
282
|
+
for (const u of usage) {
|
|
283
|
+
if (u.text.includes(prohibited))
|
|
284
|
+
findings.push({ ruleId: "DV-PROHIB-001", severity: "P0", file: u.where, detail: `prohibited command in executable surface` });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let suppressed = 0;
|
|
290
|
+
let active = findings;
|
|
291
|
+
if (baselinePath) {
|
|
292
|
+
const baseline = new Set(JSON.parse(fs.readFileSync(baselinePath, "utf8")));
|
|
293
|
+
// Fingerprint includes the detail so one baselined finding never hides a
|
|
294
|
+
// different finding of the same rule in the same file.
|
|
295
|
+
active = findings.filter((f) => {
|
|
296
|
+
const hit = baseline.has(`${f.ruleId}:${f.file}:${f.detail}`) || baseline.has(`${f.ruleId}:${f.file}`);
|
|
297
|
+
if (hit) suppressed += 1;
|
|
298
|
+
return !hit;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const counts = { P0: 0, P1: 0, P2: 0 };
|
|
303
|
+
for (const f of active) counts[f.severity] += 1;
|
|
304
|
+
return { version: 1, profile, profileSource: profileOverride ? "override" : detected.source, root: path.resolve(root), counts, suppressed, findings: active };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function main() {
|
|
308
|
+
let args;
|
|
309
|
+
try {
|
|
310
|
+
args = parseArgs({
|
|
311
|
+
options: {
|
|
312
|
+
root: { type: "string", default: "." },
|
|
313
|
+
profile: { type: "string" },
|
|
314
|
+
baseline: { type: "string" },
|
|
315
|
+
json: { type: "boolean", default: false },
|
|
316
|
+
out: { type: "string" },
|
|
317
|
+
help: { type: "boolean", default: false },
|
|
318
|
+
},
|
|
319
|
+
allowPositionals: false,
|
|
320
|
+
}).values;
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.error(String(err.message ?? err));
|
|
323
|
+
console.error(USAGE);
|
|
324
|
+
process.exit(2);
|
|
325
|
+
}
|
|
326
|
+
if (args.help) {
|
|
327
|
+
console.log(USAGE);
|
|
328
|
+
process.exit(0);
|
|
329
|
+
}
|
|
330
|
+
if (!fs.existsSync(args.root) || !fs.statSync(args.root).isDirectory()) {
|
|
331
|
+
console.error(`root is not a directory: ${args.root}`);
|
|
332
|
+
console.error(USAGE);
|
|
333
|
+
process.exit(2);
|
|
334
|
+
}
|
|
335
|
+
if (args.baseline && !fs.existsSync(args.baseline)) {
|
|
336
|
+
console.error(`baseline file not found: ${args.baseline}`);
|
|
337
|
+
process.exit(2);
|
|
338
|
+
}
|
|
339
|
+
if (args.profile && !KNOWN_PROFILES.has(args.profile)) {
|
|
340
|
+
console.error(`unknown profile: ${args.profile} (known: ${[...KNOWN_PROFILES].join(", ")})`);
|
|
341
|
+
console.error(USAGE);
|
|
342
|
+
process.exit(2);
|
|
343
|
+
}
|
|
344
|
+
const report = validateDocs(args.root, { profile: args.profile, baselinePath: args.baseline });
|
|
345
|
+
if (args.json) console.log(JSON.stringify(report, null, 2));
|
|
346
|
+
else {
|
|
347
|
+
console.log(`docs validation - profile ${report.profile}, ${report.findings.length} finding(s) (P0=${report.counts.P0} P1=${report.counts.P1} P2=${report.counts.P2}${report.suppressed ? `, ${report.suppressed} baseline-suppressed` : ""})`);
|
|
348
|
+
for (const f of report.findings) console.log(` [${f.severity}] ${f.ruleId} ${f.file} - ${f.detail}`);
|
|
349
|
+
if (!report.findings.length) console.log(" clean");
|
|
350
|
+
}
|
|
351
|
+
if (args.out) {
|
|
352
|
+
const outDir = path.dirname(path.resolve(args.out));
|
|
353
|
+
const tmpOut = path.join(outDir, `.tmp-report-${process.pid}`);
|
|
354
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
355
|
+
fs.writeFileSync(tmpOut, JSON.stringify(report, null, 2) + "\n");
|
|
356
|
+
fs.renameSync(tmpOut, path.resolve(args.out));
|
|
357
|
+
}
|
|
358
|
+
process.exit(report.findings.length ? 1 : 0);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Main-module guard: realpath both sides - Node resolves symlinks (e.g. /tmp
|
|
362
|
+
// on macOS) when building import.meta.url for the entry module.
|
|
363
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href) main();
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doctor
|
|
3
|
+
description: Check whether Superdev can actually work here, and report exactly what to do when it cannot. Use for "is superdev set up correctly?", "why is this failing to start?", "check my environment", "is provider X available?", "the database will not open", "the service is not responding", or before substantial work in an unfamiliar environment. Two programs answer different halves: the CLI doctor reports the storage engine, database integrity, schema version, documentation sync and record alignment; the capability inspector reports harness lifecycle coverage and readiness for every specialist provider. Read only by default, so it installs nothing, enables nothing, decrypts nothing, and reports an unproven state as unproven rather than as ready.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Doctor
|
|
7
|
+
|
|
8
|
+
Report the truthful state of everything Superdev depends on, with the exact
|
|
9
|
+
command that fixes each gap. Never install anything without consent to a named
|
|
10
|
+
plan.
|
|
11
|
+
|
|
12
|
+
`SD` means the installed `superdev` command, with `--root <project>` on
|
|
13
|
+
every call.
|
|
14
|
+
|
|
15
|
+
## Two programs, two questions
|
|
16
|
+
|
|
17
|
+
They are separate on purpose and neither reports the other's subject. Running
|
|
18
|
+
one and presenting it as the whole picture is the mistake this section exists to
|
|
19
|
+
prevent.
|
|
20
|
+
|
|
21
|
+
**Is this project healthy?**
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
SD doctor --root <project> [--json]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Reports: storage engine, database and schema version, migration history drift,
|
|
28
|
+
integrity, documentation sync, record alignment and freshness. Exit `1` when a
|
|
29
|
+
check finds a problem.
|
|
30
|
+
|
|
31
|
+
With `--json`, read the **exit code** or `data.ok`. The envelope's top level
|
|
32
|
+
`ok` reports only that the command ran, so it says `true` on a run where a check
|
|
33
|
+
failed. Each check carries `{ name, ok, detail }` and no remediation field: the
|
|
34
|
+
fixes below are this skill's knowledge, not the tool's output.
|
|
35
|
+
|
|
36
|
+
**Can this machine run Superdev's specialist work?**
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/doctor/doctor.mjs" inspect --root <project> [--json]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Reports: which harnesses are present and ready, which providers are installed
|
|
43
|
+
and invocable here, and what lifecycle automation actually fires. Read only. It
|
|
44
|
+
writes nothing and installs nothing.
|
|
45
|
+
|
|
46
|
+
It exits `1` whenever any provider is not ready, which on a machine that has
|
|
47
|
+
deliberately not installed one is a normal state rather than a fault. Read the
|
|
48
|
+
report, not the exit code.
|
|
49
|
+
|
|
50
|
+
## Steps
|
|
51
|
+
|
|
52
|
+
1. **Run both.** A healthy project database says nothing about whether a
|
|
53
|
+
provider is installed, and a ready provider says nothing about whether the
|
|
54
|
+
database opens.
|
|
55
|
+
2. **Read every section**, not just the failing one. A passing engine with a
|
|
56
|
+
failing integrity check is not a healthy project.
|
|
57
|
+
3. **Report each gap with its remediation**, in the order that unblocks the most
|
|
58
|
+
work.
|
|
59
|
+
4. **Take consent separately** for anything that installs or changes the
|
|
60
|
+
machine. One named plan, one informed yes. Never `--all`, never a silent
|
|
61
|
+
`-y`, and strip those flags from any command you relay.
|
|
62
|
+
5. **Re-run** afterwards to prove the gap closed. A plan is not evidence; a
|
|
63
|
+
passing re-run is.
|
|
64
|
+
|
|
65
|
+
## What `SD doctor` checks
|
|
66
|
+
|
|
67
|
+
**Storage engine.** Reported rather than tested: reaching doctor at all proves
|
|
68
|
+
the engine loaded, so this row is always a pass and exists to say so. Superdev
|
|
69
|
+
has exactly one engine and no fallback, and nothing degrades quietly to a second
|
|
70
|
+
one, because there is no second one.
|
|
71
|
+
|
|
72
|
+
The failing case never reaches doctor. When the engine is missing no command runs
|
|
73
|
+
at all, and every one of them says so in the same sentence rather than failing
|
|
74
|
+
with a package resolver error. The usual cause
|
|
75
|
+
is worth knowing: a Claude Code marketplace install **copies** the plugin into
|
|
76
|
+
the harness's own cache, and `node_modules` is git-ignored, so the copy arrives
|
|
77
|
+
with no engine. The fix is `npm install` in that copied directory, not in the
|
|
78
|
+
original checkout. Nothing installs it automatically.
|
|
79
|
+
|
|
80
|
+
**Database.** File presence, schema version, applied migrations, drift in the
|
|
81
|
+
recorded migration history, integrity and foreign-key check. `SD db status` is
|
|
82
|
+
the same ground on its own. Repairs:
|
|
83
|
+
|
|
84
|
+
- Schema behind the plugin: `SD db migrate --apply` (it backs up first).
|
|
85
|
+
- Corrupt or unopenable: `SD db restore <file> --apply` from a rolling backup
|
|
86
|
+
under `.superdev/`, or `SD import <file> --apply` from a portable export.
|
|
87
|
+
- Before any risky maintenance: `SD db backup --apply`.
|
|
88
|
+
|
|
89
|
+
`db migrate`, `db restore` and `import` all refuse while the control center
|
|
90
|
+
holds the database. Each refusal names the command that frees it.
|
|
91
|
+
|
|
92
|
+
**Documentation and alignment.** Whether generated Markdown still matches the
|
|
93
|
+
database, whether any record maps to nothing that declares it, and whether the
|
|
94
|
+
generated files were built from an older revision than the database now carries.
|
|
95
|
+
|
|
96
|
+
**Local service.** `SD services` lists what is running and on which port. `SD
|
|
97
|
+
start`, `SD stop` and `SD restart` manage the one for this project. A running
|
|
98
|
+
service never locks out the command line: readers open read only, writers hold
|
|
99
|
+
the lock for milliseconds.
|
|
100
|
+
|
|
101
|
+
## What the capability inspector checks
|
|
102
|
+
|
|
103
|
+
**Harness lifecycle coverage.** What can actually fire here, measured rather
|
|
104
|
+
than assumed:
|
|
105
|
+
|
|
106
|
+
- Claude Code: session hooks are active.
|
|
107
|
+
- Codex: hooks require explicit trust, and its tool hooks cover shell commands
|
|
108
|
+
only, so edit-triggered staleness marking never fires there.
|
|
109
|
+
- skills.sh: no hook mechanism at all, so the orchestrator must run `SD resume`
|
|
110
|
+
and the before-implementation sequence explicitly.
|
|
111
|
+
|
|
112
|
+
Report this honestly rather than implying automation that will not happen, and
|
|
113
|
+
name the explicit command that covers the gap.
|
|
114
|
+
|
|
115
|
+
**Providers.** Superpowers, Claude Mem, Frontend Design, Impeccable, Find Skills
|
|
116
|
+
and skills.sh, Task Observer, and envx. For each: whether it is installed and
|
|
117
|
+
whether it is invocable *here*, with its resolved version or `unknown`. Those are
|
|
118
|
+
different states and are reported separately. An unproven state is reported as
|
|
119
|
+
unproven, never as ready.
|
|
120
|
+
|
|
121
|
+
`inspect`, `setup` and `plan` all write nothing and install nothing: `setup`
|
|
122
|
+
summarises readiness and `plan` produces a plan and a `planId` without executing
|
|
123
|
+
it. Only `apply` and `lock` change anything, `apply` by running an approved
|
|
124
|
+
plan's commands and `lock` by writing a capabilities file. Reach for `inspect`
|
|
125
|
+
unless the user has asked to install something.
|
|
126
|
+
|
|
127
|
+
## Installing a provider
|
|
128
|
+
|
|
129
|
+
Never implicitly, and never as a side effect of a check.
|
|
130
|
+
|
|
131
|
+
`doctor.mjs plan` computes a `planId` over the exact commands it intends to run.
|
|
132
|
+
`doctor.mjs apply` refuses unless both `--plan-id` and `--consent` are given and
|
|
133
|
+
the recomputed id still matches. Commands run as argument arrays rather than
|
|
134
|
+
through a shell, and a command carrying shell metacharacters or a blanket flag
|
|
135
|
+
such as `--all`, `-y` or `--yes` is rejected outright.
|
|
136
|
+
|
|
137
|
+
**Environment safety.** envx is detected the same way every other provider is,
|
|
138
|
+
by whether its CLI is present and invocable. Neither program opens an envx stage
|
|
139
|
+
file or reads its configuration, so do not report on the contents of either.
|
|
140
|
+
Never decrypt anything, never print a value, and never list a secret name that
|
|
141
|
+
the project has not already committed to a file the user can read.
|
|
142
|
+
|
|
143
|
+
## Interpreting the result
|
|
144
|
+
|
|
145
|
+
- A missing provider is a truthful degradation, not a substitution. Say which
|
|
146
|
+
specialist pass will not run and what Superdev will do instead.
|
|
147
|
+
- A recorded readiness lock that disagrees with live state loses: live state
|
|
148
|
+
wins and the disagreement is reported.
|
|
149
|
+
- Do not report a capability as working because it worked in a previous session
|
|
150
|
+
on another machine.
|
|
151
|
+
|
|
152
|
+
## Boundaries
|
|
153
|
+
|
|
154
|
+
- Read only by default. No installs, no updates, no enabling a disabled plugin
|
|
155
|
+
on the user's behalf, no writing project files, no network calls to unrelated
|
|
156
|
+
services.
|
|
157
|
+
- Never tell anyone to run tests for the Superdev plugin. It has none, by
|
|
158
|
+
design. Deterministic validators check artifacts; they are not a test suite
|
|
159
|
+
and they do not prove product behavior.
|
|
160
|
+
- Products built with Superdev do get tests, derived from their accepted product
|
|
161
|
+
test plan. That is the product's tooling, not the plugin's.
|
|
162
|
+
|
|
163
|
+
*Standalone note: on skills.sh, install the generated `superdev` package and run
|
|
164
|
+
`npm install` inside it. It carries the orchestrator, the Docs capability and the
|
|
165
|
+
runtime, and needs no repository. A single skill copied out on its own has no
|
|
166
|
+
runtime; say which command was unavailable rather than working around it
|
|
167
|
+
silently.*
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature
|
|
3
|
+
description: Specify new or changed product behavior before it is built, at a depth proportionate to its risk. Use for "add X", "build Y", "change how Z works", "users should be able to ...", or any request that creates or alters what the product does. Picks microspec, standard spec or full design; checks governing decisions first; interviews only for what is genuinely undecided; records the feature with its module, milestone, goals, acceptance criteria, edge cases, workflows, surfaces and UI actions, API operations, data entities, migrations, integrations, jobs, permissions and NFRs in the database; then derives tasks from what was accepted. Bug fixes route to debug and behavior-preserving refactors need no feature interview.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Feature Specification
|
|
7
|
+
|
|
8
|
+
New behavior gets an accepted specification before it gets code. The database
|
|
9
|
+
then derives the work from that specification, so a feature that was never
|
|
10
|
+
specified cannot produce a valid task.
|
|
11
|
+
|
|
12
|
+
`SD` means the installed `superdev` command, with `--root <project>` on
|
|
13
|
+
every call.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
1. **Confirm this is feature work.** A defect routes to `debug`. A
|
|
18
|
+
behavior-preserving refactor needs no interview, only a task linked to what
|
|
19
|
+
it touches. Everything that changes what the product does belongs here.
|
|
20
|
+
2. **Check governing decisions.** `SD decision list --json`, then read the ones
|
|
21
|
+
touching this capability, its dependencies or its files. A conflict routes to
|
|
22
|
+
the `decision` skill before any specification work, not after.
|
|
23
|
+
3. **Read what already exists.** `SD status --json` for the modules, milestones
|
|
24
|
+
and features already accepted. New behavior usually extends a feature rather
|
|
25
|
+
than creating one.
|
|
26
|
+
4. **Pick the depth** (brief section 9.2). Depth determines which fields and
|
|
27
|
+
child records are required before acceptance:
|
|
28
|
+
- **Microspec**: small, local, reversible behavior. Purpose, user, scope,
|
|
29
|
+
primary flow, acceptance criteria, affected contracts, edge and error
|
|
30
|
+
behavior, verification.
|
|
31
|
+
- **Standard spec**: normal cross-component behavior. Adds surfaces and UI
|
|
32
|
+
actions, API operations, data entities, roles and permissions, workflow and
|
|
33
|
+
its steps, state transitions, non-happy paths, observability, rollout, test
|
|
34
|
+
plan.
|
|
35
|
+
- **Full design**: architecture, authentication, tenancy, public contracts,
|
|
36
|
+
migrations, privacy, security, billing, high-risk integrations. Adds
|
|
37
|
+
alternatives considered, sequence and state diagrams, migration and
|
|
38
|
+
rollback plan, capacity and performance targets, failure recovery,
|
|
39
|
+
operations, compatibility, and one or more decision records.
|
|
40
|
+
5. **Interview for what is undecided only.** Plain language, why the answer
|
|
41
|
+
matters, a recommended default, an example when it helps. Batch three to
|
|
42
|
+
five; ask one at a time for anything irreversible. "I do not know" becomes
|
|
43
|
+
the default plus a recorded assumption.
|
|
44
|
+
6. **Route the specialist passes.** Superpowers brainstorming shapes the
|
|
45
|
+
behavior, Superpowers planning shapes the implementation approach. Check
|
|
46
|
+
readiness with `SD doctor`. If a provider is not ready, say so, run the
|
|
47
|
+
interview here instead, and never present your own work as that provider's
|
|
48
|
+
methodology.
|
|
49
|
+
7. **Record the specification.** `SD plan` presents new and changed features for
|
|
50
|
+
acceptance and stores what is accepted (`SD plan --help` for scoping it to
|
|
51
|
+
one module or feature). Every feature belongs to exactly one module and
|
|
52
|
+
exactly one delivery milestone, and supports at least one goal. A capability
|
|
53
|
+
that genuinely spans two stages becomes two bounded features with a
|
|
54
|
+
dependency between them, never one feature with two milestones.
|
|
55
|
+
8. **Fill the contracts the depth requires.** `SD feature specify <FEAT-id>`
|
|
56
|
+
writes the six microspec covers: `--purpose`, `--user`, `--in`, `--out`,
|
|
57
|
+
`--flow`, `--criterion "what || how it is verified"` and
|
|
58
|
+
`--edge category:behavior`. `SD feature depth <FEAT-id>` says what is still
|
|
59
|
+
missing, and `SD feature depth <FEAT-id> <depth>` changes how much is owed.
|
|
60
|
+
The deeper covers are their own records: workflows and steps, surfaces and
|
|
61
|
+
their states, UI actions, API operations, data entities and fields,
|
|
62
|
+
migrations, integrations, jobs and webhooks, roles and permissions, NFRs,
|
|
63
|
+
observability, and the product test plan. All of them become the contract
|
|
64
|
+
that tasks link to with `SD task update <TASK-id> --link type:id`.
|
|
65
|
+
9. **Generate the documentation.** `SD docs generate`, then `SD docs diff` to
|
|
66
|
+
confirm nothing on disk is now an unreviewed proposal.
|
|
67
|
+
10. **Derive the work.** `SD derive <FEAT-id>`, present the derived
|
|
68
|
+
plan for acceptance, then hand to the `task` skill for the
|
|
69
|
+
before-implementation sequence. Implementation starts there, never here.
|
|
70
|
+
|
|
71
|
+
## Module completeness
|
|
72
|
+
|
|
73
|
+
For each module, capture or deliberately mark not applicable: pages and
|
|
74
|
+
surfaces; UI composition; actions; API surface; data; end-to-end wiring; state
|
|
75
|
+
machines; events; edge cases; UI states; telemetry; accessibility;
|
|
76
|
+
internationalization; feature flags; responsive behavior; user-facing copy; URL
|
|
77
|
+
state and deep links; performance; discoverability and SEO; compliance and
|
|
78
|
+
product tests.
|
|
79
|
+
|
|
80
|
+
"Not yet considered" is not an answer. Either it is specified, or it is marked
|
|
81
|
+
not applicable with a reason, or it is an open question with an owner.
|
|
82
|
+
|
|
83
|
+
## UI-bearing features
|
|
84
|
+
|
|
85
|
+
A surface is not specified until every interactive element on it has: label,
|
|
86
|
+
purpose, the role and permission that may use it, precondition, the action it
|
|
87
|
+
performs, input and validation, the API or local effect, and its loading,
|
|
88
|
+
disabled, empty, success, error and offline states, plus keyboard behavior,
|
|
89
|
+
accessible name, focus behavior, responsive behavior, and confirmation for
|
|
90
|
+
anything destructive. Telemetry only when explicitly approved.
|
|
91
|
+
|
|
92
|
+
Route direction and implementation to **Frontend Design**, and critique,
|
|
93
|
+
accessibility, interaction quality and polish to **Impeccable**. Check both with
|
|
94
|
+
`SD doctor`. Name each provider that ran and each that did not, with the reason.
|
|
95
|
+
A run that says nothing about a provider is not a passing run.
|
|
96
|
+
|
|
97
|
+
Validate against the real interface, not only headless assertions: exercise each
|
|
98
|
+
contracted state, keyboard navigation, focus restoration, accessible names and
|
|
99
|
+
responsive behavior, and keep the screenshot or recording as the evidence
|
|
100
|
+
attached to the task.
|
|
101
|
+
|
|
102
|
+
## Changing an accepted feature
|
|
103
|
+
|
|
104
|
+
Specification first, then the task delta.
|
|
105
|
+
|
|
106
|
+
1. Update the feature and its contracts, and take acceptance for the change.
|
|
107
|
+
2. `SD docs generate` so the projection matches.
|
|
108
|
+
3. `SD derive <FEAT-id>` regenerates the task plan as a delta. The feature is
|
|
109
|
+
positional, not a flag.
|
|
110
|
+
4. Completed tasks are never rewritten. They are superseded or reopened with
|
|
111
|
+
their history intact.
|
|
112
|
+
|
|
113
|
+
Work that appears mid-implementation and is outside the accepted scope stops and
|
|
114
|
+
comes back here. It must not live only in the conversation or in the code.
|
|
115
|
+
|
|
116
|
+
## Boundaries
|
|
117
|
+
|
|
118
|
+
- A feature is complete only when its accepted contract components are complete
|
|
119
|
+
and its acceptance criteria carry current evidence. Progress is derived; there
|
|
120
|
+
is no command to declare a feature done.
|
|
121
|
+
- No invented stack specifics. Vendors, frameworks, budgets, regions and
|
|
122
|
+
compliance scopes come from repository evidence or an accepted decision.
|
|
123
|
+
- Product tests come from the accepted product test plan and the project's own
|
|
124
|
+
tooling. Never invent a framework because it is popular, and never write a
|
|
125
|
+
test task for the Superdev plugin.
|
|
126
|
+
- Never store secrets, personal data, private identifiers or absolute machine
|
|
127
|
+
paths in a specification.
|
|
128
|
+
|
|
129
|
+
*Standalone note: on skills.sh, install the generated `superdev` package. It
|
|
130
|
+
carries the orchestrator, the Docs capability and the runtime, and needs no
|
|
131
|
+
repository. A single skill copied out on its own has no runtime; say which
|
|
132
|
+
command was unavailable rather than working around it silently.*
|