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,315 @@
1
+ // What version this is, and whether a newer one exists.
2
+ //
3
+ // Two separate questions, and only the first can be answered offline. Keeping
4
+ // them in one module keeps the one that touches the network in a single place a
5
+ // reader can audit, rather than spread across the commands that report it.
6
+ //
7
+ // The update check is the only outbound request this product makes. Everything
8
+ // about it is arranged so that it cannot become a condition of working:
9
+ //
10
+ // It never blocks. The answer comes from a file written by the previous
11
+ // check, so a command reports what was already known and never waits.
12
+ // A command that had to wait on a registry would make an offline machine
13
+ // slower than an online one, which is the wrong way round for a tool whose
14
+ // first promise is that it works with no network.
15
+ //
16
+ // It runs at most once a day, and only when something already ran.
17
+ //
18
+ // It fails silently. No network, a proxy, a firewall, a registry outage: the
19
+ // check writes nothing and the next command behaves as though it had never
20
+ // been asked.
21
+ //
22
+ // It can be turned off, and says so the first time it runs.
23
+ //
24
+ // What it sends is a GET for one package's metadata. What that discloses is
25
+ // that somebody uses Superdev. That is a real disclosure, small but not zero,
26
+ // which is why it is documented in the README rather than buried here.
27
+
28
+ import { spawn } from "node:child_process";
29
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
30
+ import { dirname, join } from "node:path";
31
+ import { fileURLToPath } from "node:url";
32
+
33
+ /** The package this CLI was published as, read from its own manifest. */
34
+ const HERE = dirname(fileURLToPath(import.meta.url));
35
+ const MANIFEST = join(HERE, "..", "..", "package.json");
36
+
37
+ let cached = null;
38
+
39
+ /** This CLI's own name and version. Always available, never a network call. */
40
+ export function self() {
41
+ if (cached) return cached;
42
+ try {
43
+ const manifest = JSON.parse(readFileSync(MANIFEST, "utf8"));
44
+ cached = {
45
+ name: manifest.name ?? "superdev-cli",
46
+ version: manifest.version ?? "0.0.0",
47
+ root: join(HERE, "..", ".."),
48
+ };
49
+ } catch {
50
+ // A tree with no manifest still has to report something rather than crash.
51
+ cached = { name: "superdev-cli", version: "unknown", root: join(HERE, "..", "..") };
52
+ }
53
+ return cached;
54
+ }
55
+
56
+ /**
57
+ * Compare two semantic versions. True when `candidate` is newer than `current`.
58
+ *
59
+ * Pre-releases are the part worth spelling out, because the first version of
60
+ * this dropped them from both sides and so reported 1.0.0 and 1.0.0-beta.1 as
61
+ * the same version. The consequence was silence exactly where a notice matters
62
+ * most: somebody running a pre-release was never told the release had shipped.
63
+ *
64
+ * The rule is the one semver states. A release outranks any pre-release of the
65
+ * same numbers, and between two pre-releases the identifiers are compared left
66
+ * to right, numerically where both are numeric and otherwise as text.
67
+ */
68
+ export function isNewer(candidate, current) {
69
+ const a = parse(candidate);
70
+ const b = parse(current);
71
+ // A version nobody can read is not evidence that an update exists. Without
72
+ // this, an unreadable version left minor and patch undefined, every
73
+ // comparison went through NaN, and the answer came back false by accident
74
+ // rather than by decision. False is the right answer; it should be given on
75
+ // purpose, and only after saying why.
76
+ if (!a || !b) return false;
77
+
78
+ if (a.major !== b.major) return a.major > b.major;
79
+ if (a.minor !== b.minor) return a.minor > b.minor;
80
+ if (a.patch !== b.patch) return a.patch > b.patch;
81
+
82
+ // Same numbers. A release beats a pre-release of them; a pre-release loses to
83
+ // the release and is compared identifier by identifier against another.
84
+ if (!a.pre && !b.pre) return false;
85
+ if (!a.pre) return true;
86
+ if (!b.pre) return false;
87
+ return comparePre(a.pre, b.pre) > 0;
88
+ }
89
+
90
+ /** A version split into its parts, or null when it is not a version at all. */
91
+ function parse(value) {
92
+ const text = String(value ?? "").trim();
93
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(text);
94
+ if (!match) return null;
95
+ return {
96
+ major: Number(match[1]),
97
+ minor: Number(match[2]),
98
+ patch: Number(match[3]),
99
+ pre: match[4] ?? "",
100
+ };
101
+ }
102
+
103
+ /** Semver pre-release ordering: numeric parts numerically, the rest as text. */
104
+ function comparePre(left, right) {
105
+ const a = left.split(".");
106
+ const b = right.split(".");
107
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
108
+ // A shorter set of identifiers is the lower precedence, so a missing part
109
+ // loses rather than being treated as zero.
110
+ if (a[i] === undefined) return -1;
111
+ if (b[i] === undefined) return 1;
112
+ if (a[i] === b[i]) continue;
113
+ const numericA = /^\d+$/.test(a[i]);
114
+ const numericB = /^\d+$/.test(b[i]);
115
+ if (numericA && numericB) return Number(a[i]) - Number(b[i]);
116
+ // A numeric identifier always has lower precedence than an alphanumeric one.
117
+ if (numericA) return -1;
118
+ if (numericB) return 1;
119
+ return a[i] < b[i] ? -1 : 1;
120
+ }
121
+ return 0;
122
+ }
123
+
124
+ // ------------------------------------------------------------------ settings
125
+
126
+ const stateFile = (root) => join(root, ".superdev", "runtime", "update-check.json");
127
+
128
+ const readState = (root) => {
129
+ try {
130
+ return JSON.parse(readFileSync(stateFile(root), "utf8"));
131
+ } catch {
132
+ return {};
133
+ }
134
+ };
135
+
136
+ const writeState = (root, state) => {
137
+ try {
138
+ mkdirSync(dirname(stateFile(root)), { recursive: true });
139
+ writeFileSync(stateFile(root), JSON.stringify(state, null, 1));
140
+ } catch {
141
+ // A read-only project directory is not a reason to fail a command.
142
+ }
143
+ };
144
+
145
+ /**
146
+ * Whether checking is allowed at all.
147
+ *
148
+ * The environment variable wins, because that is what somebody reaches for in
149
+ * a build, a container or a locked-down network where an outbound request is
150
+ * not merely unwanted but refused.
151
+ */
152
+ export function checkingEnabled(root) {
153
+ if (process.env.SUPERDEV_NO_UPDATE_CHECK) return false;
154
+ if (process.env.NO_UPDATE_NOTIFIER) return false;
155
+ if (process.env.CI) return false;
156
+ return readState(root).disabled !== true;
157
+ }
158
+
159
+ export function setChecking(root, enabled) {
160
+ writeState(root, { ...readState(root), disabled: !enabled });
161
+ return enabled;
162
+ }
163
+
164
+ const DAY_MS = 24 * 60 * 60 * 1000;
165
+
166
+ // --------------------------------------------------------------- the notice
167
+
168
+ /**
169
+ * The notice to print, from what the last check found. Never makes a request.
170
+ *
171
+ * Returns null when there is nothing to say, which is the usual case, so a
172
+ * caller can print it unconditionally without deciding anything.
173
+ */
174
+ export function pendingNotice(root) {
175
+ const state = readState(root);
176
+ if (!checkingEnabled(root)) return null;
177
+ const lines = [];
178
+ const me = self();
179
+
180
+ if (state.cli && isNewer(state.cli, me.version)) {
181
+ lines.push(
182
+ `A newer Superdev CLI is available: ${state.cli}, you have ${me.version}. Update with npm install -g ${me.name}.`,
183
+ );
184
+ }
185
+ if (state.plugin && state.pluginCurrent && isNewer(state.plugin, state.pluginCurrent)) {
186
+ lines.push(
187
+ `A newer Superdev plugin is available: ${state.plugin}, you have ${state.pluginCurrent}. Update with claude plugin marketplace update superdev.`,
188
+ );
189
+ }
190
+ if (!lines.length) return null;
191
+ if (!state.told) {
192
+ lines.push("Turn these off with superdev settings --no-update-check, or set SUPERDEV_NO_UPDATE_CHECK.");
193
+ writeState(root, { ...state, told: true });
194
+ }
195
+ return lines.join("\n");
196
+ }
197
+
198
+ /**
199
+ * Start a check in a process this one does not wait for.
200
+ *
201
+ * Firing the request here and not awaiting it is not enough: a pending fetch
202
+ * keeps the event loop alive, so the command still paid for the round trip
203
+ * before it could exit. Measured at 573 ms against 136 ms warm, which is a
204
+ * courtesy charging half a second for itself.
205
+ *
206
+ * So the work happens in a detached child that is unref'd immediately. The
207
+ * parent exits as though nothing had been asked, and whatever the child learns
208
+ * is read by the next command from the file it writes.
209
+ */
210
+ export function startRefresh(root, { pluginVersion = null } = {}) {
211
+ if (!checkingEnabled(root)) return false;
212
+ const state = readState(root);
213
+ const age = state.checkedAt ? Date.now() - Date.parse(state.checkedAt) : Infinity;
214
+ if (age < DAY_MS) return false;
215
+ // Stamped before the child runs, so a machine with no network does not spawn
216
+ // a process on every single command.
217
+ writeState(root, { ...state, checkedAt: new Date().toISOString() });
218
+
219
+ try {
220
+ const child = spawn(
221
+ process.execPath,
222
+ [fileURLToPath(import.meta.url), "--refresh", root, pluginVersion ?? ""],
223
+ { detached: true, stdio: "ignore" },
224
+ );
225
+ child.unref();
226
+ return true;
227
+ } catch {
228
+ return false;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Ask the registry and the plugin marketplace what the latest versions are.
234
+ *
235
+ * Runs in the detached child, or directly when something wants to wait for it.
236
+ * Every failure is swallowed: this is a courtesy, and a courtesy that
237
+ * interrupts is worse than none.
238
+ */
239
+ export async function refreshInBackground(root, { pluginVersion = null, force = false } = {}) {
240
+ if (!checkingEnabled(root)) return null;
241
+ const state = readState(root);
242
+ const age = state.checkedAt ? Date.now() - Date.parse(state.checkedAt) : Infinity;
243
+ if (!force && age < DAY_MS) return null;
244
+
245
+ const me = self();
246
+ const next = { ...state, checkedAt: new Date().toISOString() };
247
+ if (pluginVersion) next.pluginCurrent = pluginVersion;
248
+
249
+ const [cli, plugin] = await Promise.all([
250
+ latestOnNpm(me.name),
251
+ latestPluginVersion(),
252
+ ]);
253
+ if (cli) next.cli = cli;
254
+ if (plugin) next.plugin = plugin;
255
+
256
+ writeState(root, next);
257
+ return next;
258
+ }
259
+
260
+ /** The newest published version of a package, or nothing. */
261
+ async function latestOnNpm(name) {
262
+ return withTimeout(async (signal) => {
263
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, {
264
+ signal,
265
+ headers: { accept: "application/vnd.npm.install-v1+json" },
266
+ });
267
+ if (!response.ok) return null;
268
+ const body = await response.json();
269
+ return typeof body?.version === "string" ? body.version : null;
270
+ });
271
+ }
272
+
273
+ /**
274
+ * The plugin version the marketplace advertises.
275
+ *
276
+ * Read from the repository's own manifest over HTTPS rather than through git,
277
+ * because a clone is far more work than reading one small file and this is only
278
+ * ever a courtesy.
279
+ */
280
+ async function latestPluginVersion() {
281
+ return withTimeout(async (signal) => {
282
+ const response = await fetch(
283
+ "https://raw.githubusercontent.com/superdev-ai/superdev/main/.claude-plugin/plugin.json",
284
+ { signal, headers: { accept: "application/json" } },
285
+ );
286
+ if (!response.ok) return null;
287
+ const body = await response.json();
288
+ return typeof body?.version === "string" ? body.version : null;
289
+ });
290
+ }
291
+
292
+ /** Three seconds, then give up. Nothing here is worth waiting longer for. */
293
+ async function withTimeout(work, ms = 3000) {
294
+ const controller = new AbortController();
295
+ const timer = setTimeout(() => controller.abort(), ms);
296
+ try {
297
+ return await work(controller.signal);
298
+ } catch {
299
+ return null;
300
+ } finally {
301
+ clearTimeout(timer);
302
+ }
303
+ }
304
+
305
+ // ------------------------------------------------------------------ the child
306
+ //
307
+ // Run as `node version.mjs --refresh <root> [pluginVersion]` by startRefresh.
308
+ // Nothing else invokes this, and it writes one small file.
309
+ if (process.argv[2] === "--refresh") {
310
+ const root = process.argv[3];
311
+ const pluginVersion = process.argv[4] || null;
312
+ if (root) {
313
+ await refreshInBackground(root, { pluginVersion, force: true }).catch(() => {});
314
+ }
315
+ }