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.
Files changed (182) hide show
  1. package/.claude-plugin/marketplace.json +33 -0
  2. package/.claude-plugin/plugin.json +21 -0
  3. package/.codex-plugin/plugin.json +27 -0
  4. package/CODE_OF_CONDUCT.md +109 -0
  5. package/CONTRIBUTING.md +205 -0
  6. package/LICENSE +202 -0
  7. package/NOTICE +11 -0
  8. package/README.md +1051 -0
  9. package/SECURITY.md +135 -0
  10. package/THIRD-PARTY-NOTICES.md +253 -0
  11. package/hooks/hooks.json +61 -0
  12. package/hooks/run.mjs +88 -0
  13. package/package.json +65 -0
  14. package/references/confidentiality.md +48 -0
  15. package/references/evidence-and-risk.md +73 -0
  16. package/references/operating-model.md +105 -0
  17. package/references/platform-capabilities.md +51 -0
  18. package/references/project-record.md +91 -0
  19. package/references/provider-contracts.md +102 -0
  20. package/scripts/doctor/doctor.mjs +539 -0
  21. package/scripts/privacy/scan-history.mjs +163 -0
  22. package/scripts/privacy/scan.mjs +368 -0
  23. package/scripts/validate/README.md +94 -0
  24. package/scripts/validate/common.mjs +149 -0
  25. package/scripts/validate/data-model.mjs +225 -0
  26. package/scripts/validate/dependencies.mjs +69 -0
  27. package/scripts/validate/docs-templates.mjs +99 -0
  28. package/scripts/validate/footprint.mjs +78 -0
  29. package/scripts/validate/imports.mjs +61 -0
  30. package/scripts/validate/manifests.mjs +173 -0
  31. package/scripts/validate/markdown.mjs +123 -0
  32. package/scripts/validate/migrations.mjs +187 -0
  33. package/scripts/validate/no-tests.mjs +136 -0
  34. package/scripts/validate/privacy.mjs +115 -0
  35. package/scripts/validate/record-links.mjs +127 -0
  36. package/scripts/validate/skill-commands.mjs +190 -0
  37. package/scripts/validate/skills.mjs +112 -0
  38. package/scripts/validate/specification.mjs +88 -0
  39. package/scripts/validate/style.mjs +78 -0
  40. package/scripts/validate/validate-all.mjs +175 -0
  41. package/skills/debug/SKILL.md +68 -0
  42. package/skills/decision/SKILL.md +99 -0
  43. package/skills/docs/SKILL.md +86 -0
  44. package/skills/docs/assets/fragments/api/events.md +14 -0
  45. package/skills/docs/assets/fragments/api/graphql.md +14 -0
  46. package/skills/docs/assets/fragments/api/local-only.md +13 -0
  47. package/skills/docs/assets/fragments/api/rest.md +13 -0
  48. package/skills/docs/assets/fragments/api/rpc.md +13 -0
  49. package/skills/docs/assets/fragments/async/event-bus.md +13 -0
  50. package/skills/docs/assets/fragments/async/none.md +11 -0
  51. package/skills/docs/assets/fragments/async/platform-jobs.md +13 -0
  52. package/skills/docs/assets/fragments/async/queue.md +13 -0
  53. package/skills/docs/assets/fragments/async/scheduler.md +13 -0
  54. package/skills/docs/assets/fragments/auth/detected-provider.md +15 -0
  55. package/skills/docs/assets/fragments/auth/neutral-base.md +14 -0
  56. package/skills/docs/assets/fragments/data/document.md +13 -0
  57. package/skills/docs/assets/fragments/data/external-saas.md +13 -0
  58. package/skills/docs/assets/fragments/data/key-value.md +13 -0
  59. package/skills/docs/assets/fragments/data/none.md +12 -0
  60. package/skills/docs/assets/fragments/data/sql-orm.md +13 -0
  61. package/skills/docs/assets/fragments/data/sql-plain.md +13 -0
  62. package/skills/docs/assets/fragments/env/conventional.md +13 -0
  63. package/skills/docs/assets/fragments/env/envx.md +13 -0
  64. package/skills/docs/assets/fragments/env/managed-platform.md +13 -0
  65. package/skills/docs/assets/fragments/env/none.md +11 -0
  66. package/skills/docs/assets/fragments/ui/api-only.md +13 -0
  67. package/skills/docs/assets/fragments/ui/cli.md +14 -0
  68. package/skills/docs/assets/fragments/ui/desktop.md +14 -0
  69. package/skills/docs/assets/fragments/ui/mobile.md +14 -0
  70. package/skills/docs/assets/fragments/ui/web.md +14 -0
  71. package/skills/docs/assets/templates/adr.md +62 -0
  72. package/skills/docs/assets/templates/api.md +30 -0
  73. package/skills/docs/assets/templates/architecture.md +52 -0
  74. package/skills/docs/assets/templates/change-impact-drift-report.md +29 -0
  75. package/skills/docs/assets/templates/compliance.md +29 -0
  76. package/skills/docs/assets/templates/data-schema.md +43 -0
  77. package/skills/docs/assets/templates/feature.md +43 -0
  78. package/skills/docs/assets/templates/foundations.md +55 -0
  79. package/skills/docs/assets/templates/jobs-webhooks.md +36 -0
  80. package/skills/docs/assets/templates/module-inventory.md +19 -0
  81. package/skills/docs/assets/templates/module.md +50 -0
  82. package/skills/docs/assets/templates/nfr.md +22 -0
  83. package/skills/docs/assets/templates/observability.md +28 -0
  84. package/skills/docs/assets/templates/pages-ui-actions.md +47 -0
  85. package/skills/docs/assets/templates/project-summary.md +44 -0
  86. package/skills/docs/assets/templates/roles-permissions.md +26 -0
  87. package/skills/docs/assets/templates/test-plan.md +23 -0
  88. package/skills/docs/assets/templates/workflow-state-machine.md +43 -0
  89. package/skills/docs/references/adr-authoring.md +24 -0
  90. package/skills/docs/references/apis-and-data.md +23 -0
  91. package/skills/docs/references/capability-fragments.md +25 -0
  92. package/skills/docs/references/change-tracking.md +62 -0
  93. package/skills/docs/references/diagrams.md +31 -0
  94. package/skills/docs/references/discovery.md +59 -0
  95. package/skills/docs/references/edge-cases.md +45 -0
  96. package/skills/docs/references/foundations-modules.md +18 -0
  97. package/skills/docs/references/ingestion.md +52 -0
  98. package/skills/docs/references/initialize-adopt.md +25 -0
  99. package/skills/docs/references/module-decomposition.md +38 -0
  100. package/skills/docs/references/profiles.md +60 -0
  101. package/skills/docs/references/quality-attributes.md +27 -0
  102. package/skills/docs/references/reverse-engineer.md +21 -0
  103. package/skills/docs/references/spec-depths.md +31 -0
  104. package/skills/docs/references/summarize.md +18 -0
  105. package/skills/docs/references/surfaces-and-actions.md +24 -0
  106. package/skills/docs/references/validation.md +47 -0
  107. package/skills/docs/references/workflows-and-jobs.md +25 -0
  108. package/skills/docs/scripts/ingest.mjs +984 -0
  109. package/skills/docs/scripts/profile-detect.mjs +299 -0
  110. package/skills/docs/scripts/screen.mjs +96 -0
  111. package/skills/docs/scripts/template-lint.mjs +143 -0
  112. package/skills/docs/scripts/validate-docs.mjs +363 -0
  113. package/skills/doctor/SKILL.md +167 -0
  114. package/skills/feature/SKILL.md +132 -0
  115. package/skills/init/SKILL.md +137 -0
  116. package/skills/project/SKILL.md +261 -0
  117. package/skills/project/references/commands.md +213 -0
  118. package/skills/resume/SKILL.md +79 -0
  119. package/skills/review/SKILL.md +91 -0
  120. package/skills/status/SKILL.md +85 -0
  121. package/skills/task/SKILL.md +183 -0
  122. package/src/cli/product-map.mjs +468 -0
  123. package/src/cli/render.mjs +544 -0
  124. package/src/cli.mjs +2774 -0
  125. package/src/cloud/crypto.mjs +118 -0
  126. package/src/cloud/merge.mjs +186 -0
  127. package/src/cloud/policy.mjs +115 -0
  128. package/src/cloud/sync.mjs +512 -0
  129. package/src/cloud/transport.mjs +116 -0
  130. package/src/db/connect.mjs +189 -0
  131. package/src/db/maintenance.mjs +419 -0
  132. package/src/db/migrate.mjs +188 -0
  133. package/src/db/migrations/001_initial.sql +1024 -0
  134. package/src/db/migrations/002_docs_coverage.sql +126 -0
  135. package/src/db/migrations/003_memory_retrieval_and_integrity.sql +168 -0
  136. package/src/db/migrations/004_task_categories.sql +59 -0
  137. package/src/db/migrations/005_executable_evidence.sql +32 -0
  138. package/src/db/migrations/006_module_and_feature_boundaries.sql +27 -0
  139. package/src/db/migrations/007_drop_redundant_module_users.sql +13 -0
  140. package/src/db/migrations/008_changes_assumptions_test_plans_api_services.sql +166 -0
  141. package/src/db/migrations/009_retired_documents.sql +54 -0
  142. package/src/db/migrations/010_test_plan_evidence.sql +20 -0
  143. package/src/db/migrations/011_criterion_waiver.sql +13 -0
  144. package/src/db/migrations/012_synchronization.sql +56 -0
  145. package/src/db/store.mjs +327 -0
  146. package/src/decisions/record.mjs +167 -0
  147. package/src/docs/proposals.mjs +871 -0
  148. package/src/docs/render.mjs +1424 -0
  149. package/src/docs/templates.mjs +1281 -0
  150. package/src/features/acceptance.mjs +315 -0
  151. package/src/features/specify.mjs +248 -0
  152. package/src/init/discovery.mjs +901 -0
  153. package/src/init/index.mjs +784 -0
  154. package/src/init/questions.mjs +562 -0
  155. package/src/memory/benchmark.mjs +171 -0
  156. package/src/memory/capture.mjs +135 -0
  157. package/src/memory/consolidate.mjs +255 -0
  158. package/src/memory/index.mjs +810 -0
  159. package/src/model/ids.mjs +185 -0
  160. package/src/model/screening.mjs +150 -0
  161. package/src/model/toolkit.mjs +258 -0
  162. package/src/model/vocabulary.mjs +190 -0
  163. package/src/product/assumptions.mjs +120 -0
  164. package/src/product/changes.mjs +180 -0
  165. package/src/product/test-plans.mjs +183 -0
  166. package/src/progress/index.mjs +1364 -0
  167. package/src/runtime/harness.mjs +264 -0
  168. package/src/runtime/hooks.mjs +1021 -0
  169. package/src/runtime/identity.mjs +305 -0
  170. package/src/runtime/resume.mjs +343 -0
  171. package/src/runtime/session.mjs +679 -0
  172. package/src/runtime/version.mjs +315 -0
  173. package/src/service/assets/control-center.html +206 -0
  174. package/src/service/assets/control-center.manifest.json +7 -0
  175. package/src/service/manage.mjs +542 -0
  176. package/src/service/mutations.mjs +860 -0
  177. package/src/service/read-model.mjs +1557 -0
  178. package/src/service/server.mjs +783 -0
  179. package/src/tasks/categories.mjs +154 -0
  180. package/src/tasks/derive.mjs +977 -0
  181. package/src/tasks/lifecycle.mjs +830 -0
  182. 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.*