@namewta/speculo 0.8.11 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/dist/src/cli.js +12 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/doctor.d.ts +10 -0
- package/dist/src/doctor.js +70 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/index.js +105 -56
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel.d.ts +98 -0
- package/dist/src/kernel.js +29 -0
- package/dist/src/kernel.js.map +1 -0
- package/dist/src/refresh.js +55 -24
- package/dist/src/refresh.js.map +1 -1
- package/dist/src/structured.d.ts +2 -2
- package/dist/src/structured.js +114 -241
- package/dist/src/structured.js.map +1 -1
- package/package.json +3 -2
- package/template/.speculo/README.md +1 -1
- package/template/.speculo/capabilities.json +14 -0
- package/template/.speculo/kernel/README.md +10 -0
- package/template/.speculo/kernel/capability-profile.schema.json +14 -0
- package/template/.speculo/kernel/checkpoint.schema.json +7 -0
- package/template/.speculo/kernel/trace-event.schema.json +8 -0
- package/template/.speculo/kernel/workflow-manifest.schema.json +13 -0
- package/template/.speculo/kernel.json +10 -0
- package/template/.speculo/refresh-contract.json +4 -1
- package/template/AGENTS.md +0 -3
- package/template/canonical/canonical-specdev-goal-plan.md +13 -7
- package/template/canonical/canonical-specdev-grill-with-docs.md +1 -1
- package/template/canonical/canonical-specdev-orchestrate-implementation.md +29 -11
- package/template/canonical/canonical-specdev-spec.md +1 -1
- package/template/canonical/canonical-specdev-tickets.md +38 -4
- package/template/commands/archive-and-consolidate.md +13 -40
- package/template/commands/status.md +3 -3
- package/template/skills/engineering-standards-builder/README.md +6 -28
- package/template/skills/engineering-standards-builder/SKILL.md +88 -163
- package/template/skills/engineering-standards-builder/examples/README.md +2 -0
- package/template/skills/engineering-standards-builder/manifest.txt +4 -0
- package/template/skills/engineering-standards-builder/references/rules/00-governance-and-precedence.md +22 -29
- package/template/skills/engineering-standards-builder/references/rules/01-project-discovery.md +26 -59
- package/template/skills/engineering-standards-builder/references/rules/02-evidence-topology-and-scope.md +28 -55
- package/template/skills/engineering-standards-builder/references/rules/03-interview-and-decisions.md +4 -3
- package/template/skills/engineering-standards-builder/references/rules/14-generation-contract.md +64 -80
- package/template/skills/engineering-standards-builder/references/rules/15-validation-contract.md +24 -47
- package/template/skills/engineering-standards-builder/references/rules/16-language-adapter-contract.md +11 -48
- package/template/skills/engineering-standards-builder/references/rules/README.md +3 -3
- package/template/skills/engineering-standards-builder/scripts/self-test.mjs +51 -5
- package/template/skills/engineering-standards-builder/scripts/validate-builder.mjs +16 -4
- package/template/skills/engineering-standards-builder/scripts/validate-generated-skill.mjs +166 -64
- package/template/skills/engineering-standards-builder/templates/README.md +12 -3
- package/template/skills/engineering-standards-builder/templates/domain-skill/SKILL.md.template +32 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/SKILL.md.template +16 -11
- package/template/skills/engineering-standards-builder/templates/project-skill/generated-skill-set.json.template +7 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/00-project-profile.md.template +3 -1
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/01-module-map.md.template +4 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/02-decisions-and-exceptions.md.template +1 -1
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/03-skill-map.md.template +19 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/04-source-and-template-map.md.template +22 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/review-checklist.md.template +2 -0
- package/template/skills/git-history-squash/SKILL.md +100 -0
- package/template/skills/git-history-squash/assets/request-template.json +18 -0
- package/template/skills/git-history-squash/references/recovery-contract.md +50 -0
- package/template/skills/git-history-squash/references/rewrite-contract.md +123 -0
- package/template/skills/git-history-squash/references/submodule-contract.md +54 -0
- package/template/skills/git-history-squash/scripts/git-history-squash.mjs +1171 -0
- package/template/workflows/learning/A-archive/A-archive.md +25 -0
- package/template/workflows/learning/A-assess-and-plan/A-assess-and-plan.md +17 -23
- package/template/workflows/learning/A-assess-and-plan/background-template.md +13 -0
- package/template/workflows/learning/A-assess-and-plan/change-status-template.json +21 -14
- package/template/workflows/learning/A-assess-and-plan/course-template.md +28 -0
- package/template/workflows/learning/C-consolidate/C-consolidate.md +33 -0
- package/template/workflows/learning/H-homework/H-homework.md +31 -0
- package/template/workflows/learning/H-homework/homework-template.md +63 -0
- package/template/workflows/learning/I-init-setup/I-init-setup.md +14 -19
- package/template/workflows/learning/I-init-setup/context-index-template.md +3 -3
- package/template/workflows/learning/I-init-setup/learner-profile-template.md +12 -10
- package/template/workflows/learning/I-init-setup/review-index-template.md +1 -1
- package/template/workflows/learning/INDEX.md +7 -9
- package/template/workflows/learning/L-lesson/L-lesson.md +32 -0
- package/template/workflows/learning/L-lesson/lesson-template.md +51 -0
- package/template/workflows/learning/R-review/R-review.md +13 -18
- package/template/workflows/learning/R-review/review-template.md +13 -6
- package/template/workflows/learning/README.md +86 -81
- package/template/workflows/learning/_state/status.json +1 -1
- package/template/workflows/learning/common/rules/artifact-contract.md +11 -25
- package/template/workflows/learning/common/rules/assessment-policy.md +9 -4
- package/template/workflows/learning/common/rules/knowledge-organization.md +4 -6
- package/template/workflows/learning/common/rules/mastery-policy.md +3 -19
- package/template/workflows/learning/common/rules/path-reference-contract.md +3 -5
- package/template/workflows/learning/common/rules/teaching-policy.md +9 -9
- package/template/workflows/learning/common/schemas/change-status.schema.json +46 -18
- package/template/workflows/learning/common/schemas/status.schema.json +25 -24
- package/template/workflows/learning/common/skills/topic-synthesis/SKILL.md +17 -0
- package/template/workflows/learning/common/skills/topic-synthesis/claim-template.md +15 -0
- package/template/workflows/learning/common/tools/relocate-learning.mjs +208 -0
- package/template/workflows/learning/common/tools/validate-learning.mjs +195 -297
- package/template/workflows/learning/manifest.json +1 -0
- package/template/workflows/learning/runtime-contract.json +3 -2
- package/template/workflows/ops/manifest.json +1 -0
- package/template/workflows/person/manifest.json +1 -0
- package/template/workflows/specdev/I-implement/I-implement.md +12 -4
- package/template/workflows/specdev/I-implement/execution-preflight.md +4 -0
- package/template/workflows/specdev/README.md +1 -1
- package/template/workflows/specdev/T-tickets/T-tickets.md +16 -3
- package/template/workflows/specdev/T-tickets/ticket-readiness.md +3 -0
- package/template/workflows/specdev/T-tickets/ticket-template.md +3 -0
- package/template/workflows/specdev/T-tickets/tickets-map-template.md +15 -0
- package/template/workflows/specdev/common/rules/artifact-contract.md +1 -1
- package/template/workflows/specdev/common/skills/subagent-delivery/SKILL.md +4 -2
- package/template/workflows/specdev/common/skills/subagent-delivery/references/external-web-subagent.md +2 -1
- package/template/workflows/specdev/common/skills/subagent-delivery/references/native-subagent.md +2 -1
- package/template/workflows/specdev/common/skills/subagent-delivery/references/source-package.md +4 -2
- package/template/workflows/specdev/common/tools/validate-specdev.mjs +143 -3
- package/template/workflows/specdev/manifest.json +1 -0
- package/template/workflows/workflow-manifest.schema.json +14 -0
- package/template/workflows/learning/A-archive-and-consolidate/A-archive-and-consolidate.md +0 -38
- package/template/workflows/learning/A-archive-and-consolidate/promotion-plan-template.md +0 -23
- package/template/workflows/learning/A-assess-and-plan/learning-plan-template.md +0 -28
- package/template/workflows/learning/E-eli5/E-eli5.md +0 -37
- package/template/workflows/learning/E-eli5/lesson-template.md +0 -31
- package/template/workflows/learning/P-practice/P-practice.md +0 -34
- package/template/workflows/learning/P-practice/practice-template.md +0 -16
- package/template/workflows/learning/Q-quiz/Q-quiz.md +0 -34
- package/template/workflows/learning/Q-quiz/quiz-artifact-template.md +0 -16
- package/template/workflows/learning/common/skills/knowledge-promotion/SKILL.md +0 -46
- package/template/workflows/learning/common/skills/knowledge-promotion/domain-index-template.md +0 -6
- package/template/workflows/learning/common/skills/knowledge-promotion/domain-overview-template.md +0 -19
- package/template/workflows/learning/common/skills/knowledge-promotion/knowledge-template.md +0 -25
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
|
|
8
|
+
const CHANGE_ID = /^[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
|
+
|
|
10
|
+
function args(argv) {
|
|
11
|
+
const result = { stateRoot: null, parent: null, sources: [], apply: false, rollback: null };
|
|
12
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
13
|
+
const arg = argv[index];
|
|
14
|
+
if (arg === "--state-root") result.stateRoot = resolve(argv[++index] ?? "");
|
|
15
|
+
else if (arg === "--parent") result.parent = argv[++index] ?? null;
|
|
16
|
+
else if (arg === "--source") result.sources.push(argv[++index] ?? "");
|
|
17
|
+
else if (arg === "--apply") result.apply = true;
|
|
18
|
+
else if (arg === "--rollback") result.rollback = resolve(argv[++index] ?? "");
|
|
19
|
+
else throw new Error(`unknown option: ${arg}`);
|
|
20
|
+
}
|
|
21
|
+
if (!result.stateRoot) throw new Error("--state-root is required");
|
|
22
|
+
if (result.rollback) return result;
|
|
23
|
+
if (!result.parent || !CHANGE_ID.test(result.parent) || result.sources.length === 0) {
|
|
24
|
+
throw new Error("--parent and at least one --source are required");
|
|
25
|
+
}
|
|
26
|
+
if (result.sources.some((source) => !CHANGE_ID.test(source)) || new Set(result.sources).size !== result.sources.length) {
|
|
27
|
+
throw new Error("source ids must be unique valid Change IDs");
|
|
28
|
+
}
|
|
29
|
+
if (result.sources.includes(result.parent)) throw new Error("a parent cannot be one of its own sources");
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function json(path, fallback) {
|
|
34
|
+
if (!existsSync(path)) return fallback;
|
|
35
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function files(root) {
|
|
39
|
+
const result = [];
|
|
40
|
+
async function visit(current) {
|
|
41
|
+
const entries = await (await import("node:fs/promises")).readdir(current, { withFileTypes: true });
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const path = join(current, entry.name);
|
|
44
|
+
if (entry.isDirectory()) await visit(path);
|
|
45
|
+
else if (entry.isFile()) result.push(path);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
await visit(root);
|
|
49
|
+
return result.sort();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function contentHash(root) {
|
|
53
|
+
const hash = createHash("sha256");
|
|
54
|
+
for (const path of await files(root)) {
|
|
55
|
+
const rel = relative(root, path).split(sep).join("/");
|
|
56
|
+
if (rel === ".status.json") continue;
|
|
57
|
+
hash.update(rel);
|
|
58
|
+
hash.update(await readFile(path));
|
|
59
|
+
}
|
|
60
|
+
return hash.digest("hex");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function loadLocations(stateRoot) {
|
|
64
|
+
const path = join(stateRoot, "locations.json");
|
|
65
|
+
const value = await json(path, { schema_version: 2, workflow: "learning", entries: [] });
|
|
66
|
+
if (value.schema_version !== 2 || value.workflow !== "learning" || !Array.isArray(value.entries)) {
|
|
67
|
+
throw new Error("locations.json must use Learning schema v2");
|
|
68
|
+
}
|
|
69
|
+
return { path, value };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function locatorFor(entry, stateRoot) {
|
|
73
|
+
const locator = entry?.locator;
|
|
74
|
+
if (typeof locator === "string" && (locator.startsWith("changes/") || locator.startsWith("archive/"))) return locator;
|
|
75
|
+
return `changes/${entry.change_id}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function updateLocation(value, id, from, to, hash, now, parent) {
|
|
79
|
+
const existing = value.entries.find((entry) => entry.change_id === id);
|
|
80
|
+
if (existing) {
|
|
81
|
+
existing.previous = Array.isArray(existing.previous) ? existing.previous : [];
|
|
82
|
+
existing.previous.push({ locator: from, moved_at: now, reason: "consolidate", content_hash: hash });
|
|
83
|
+
existing.locator = to;
|
|
84
|
+
existing.parent_change = parent;
|
|
85
|
+
existing.root_change = value.entries.find((entry) => entry.change_id === parent)?.root_change ?? parent;
|
|
86
|
+
existing.content_hash = hash;
|
|
87
|
+
existing.updated_at = now;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
value.entries.push({
|
|
91
|
+
change_id: id,
|
|
92
|
+
locator: to,
|
|
93
|
+
parent_change: parent,
|
|
94
|
+
root_change: parent,
|
|
95
|
+
content_hash: hash,
|
|
96
|
+
previous: [{ locator: from, moved_at: now, reason: "consolidate", content_hash: hash }],
|
|
97
|
+
updated_at: now,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function updateStatus(path, id, to, parent, now) {
|
|
102
|
+
const statusPath = join(path, ".status.json");
|
|
103
|
+
if (!existsSync(statusPath)) return;
|
|
104
|
+
const value = await json(statusPath, null);
|
|
105
|
+
if (!value || value.schema_version !== 2 || value.change_id !== id) return;
|
|
106
|
+
value.parent_change = parent;
|
|
107
|
+
value.root_change = value.root_change === id ? parent : value.root_change;
|
|
108
|
+
value.locator = to;
|
|
109
|
+
value.updated_at = now;
|
|
110
|
+
await writeFile(statusPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function updateGlobalStatus(stateRoot, ids, parent, now) {
|
|
114
|
+
const path = join(stateRoot, "status.json");
|
|
115
|
+
const value = await json(path, null);
|
|
116
|
+
if (!value || value.schema_version !== 2) return;
|
|
117
|
+
const root = value.active.find((entry) => entry.change_id === parent)?.root_change ?? parent;
|
|
118
|
+
for (const entry of [...value.active, ...value.archived]) {
|
|
119
|
+
if (!ids.includes(entry.change_id)) continue;
|
|
120
|
+
entry.parent_change = parent;
|
|
121
|
+
entry.root_change = root;
|
|
122
|
+
entry.locator = entry.locator;
|
|
123
|
+
entry.updated_at = now;
|
|
124
|
+
}
|
|
125
|
+
await writeFile(path, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function rollbackManifest(manifestPath) {
|
|
129
|
+
const manifest = await json(manifestPath, null);
|
|
130
|
+
if (!manifest || !Array.isArray(manifest.moves)) throw new Error("invalid relocation manifest");
|
|
131
|
+
for (const move of [...manifest.moves].reverse()) {
|
|
132
|
+
if (existsSync(move.to) && !existsSync(move.from)) await rename(move.to, move.from);
|
|
133
|
+
}
|
|
134
|
+
await writeFile(manifestPath, JSON.stringify({ ...manifest, rolled_back_at: new Date().toISOString() }, null, 2) + "\n", "utf8");
|
|
135
|
+
console.log(JSON.stringify({ status: "rolled-back", manifest: manifestPath }));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function main() {
|
|
139
|
+
const options = args(process.argv.slice(2));
|
|
140
|
+
if (options.rollback) {
|
|
141
|
+
await rollbackManifest(options.rollback);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const changesRoot = join(options.stateRoot, "changes");
|
|
145
|
+
const parentPath = join(changesRoot, options.parent);
|
|
146
|
+
if (!existsSync(parentPath)) throw new Error(`parent Change does not exist: ${options.parent}`);
|
|
147
|
+
const { path: locationsPath, value: locations } = await loadLocations(options.stateRoot);
|
|
148
|
+
const moves = [];
|
|
149
|
+
for (const id of options.sources) {
|
|
150
|
+
const entry = locations.entries.find((candidate) => candidate.change_id === id);
|
|
151
|
+
const fromLocator = locatorFor(entry, options.stateRoot);
|
|
152
|
+
if (!fromLocator.startsWith("changes/")) throw new Error(`source is not an active/closed Change: ${id}`);
|
|
153
|
+
const from = join(options.stateRoot, ...fromLocator.split("/"));
|
|
154
|
+
if (!existsSync(from)) throw new Error(`source Change is missing: ${fromLocator}`);
|
|
155
|
+
const relativeFrom = fromLocator.split("/");
|
|
156
|
+
if (relativeFrom.slice(0, 2).join("/") === `changes/${options.parent}` || from === parentPath || parentPath.startsWith(from + sep)) {
|
|
157
|
+
throw new Error(`source/parent cycle detected for ${id}`);
|
|
158
|
+
}
|
|
159
|
+
const to = join(parentPath, "children", id);
|
|
160
|
+
if (existsSync(to)) throw new Error(`target child already exists: ${id}`);
|
|
161
|
+
moves.push({ id, from, to, from_locator: fromLocator, to_locator: `changes/${options.parent}/children/${id}` });
|
|
162
|
+
}
|
|
163
|
+
const hashes = [];
|
|
164
|
+
for (const move of moves) hashes.push({ id: move.id, content_hash: await contentHash(move.from) });
|
|
165
|
+
const now = new Date().toISOString();
|
|
166
|
+
const manifest = {
|
|
167
|
+
schema_version: 1,
|
|
168
|
+
artifact: "learning-relocation-manifest",
|
|
169
|
+
parent_change: options.parent,
|
|
170
|
+
created_at: now,
|
|
171
|
+
moves: moves.map((move) => ({ ...move, content_hash: hashes.find((item) => item.id === move.id).content_hash })),
|
|
172
|
+
};
|
|
173
|
+
if (!options.apply) {
|
|
174
|
+
console.log(JSON.stringify({ status: "dry-run", ...manifest }, null, 2));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await mkdir(join(parentPath, "children"), { recursive: true });
|
|
179
|
+
const lockPath = join(options.stateRoot, ".consolidate.lock");
|
|
180
|
+
let lock;
|
|
181
|
+
try {
|
|
182
|
+
lock = await open(lockPath, "wx");
|
|
183
|
+
for (const move of moves) await rename(move.from, move.to);
|
|
184
|
+
for (const move of manifest.moves) {
|
|
185
|
+
updateLocation(locations, move.id, move.from_locator, move.to_locator, move.content_hash, now, options.parent);
|
|
186
|
+
await updateStatus(join(options.stateRoot, ...move.to_locator.split("/")), move.id, move.to_locator, options.parent, now);
|
|
187
|
+
}
|
|
188
|
+
await writeFile(locationsPath, JSON.stringify(locations, null, 2) + "\n", "utf8");
|
|
189
|
+
await updateGlobalStatus(options.stateRoot, options.sources, options.parent, now);
|
|
190
|
+
const manifestPath = join(parentPath, "synthesis", `relocation-${now.replaceAll(/[^0-9]/g, "").slice(0, 14)}.json`);
|
|
191
|
+
await mkdir(dirname(manifestPath), { recursive: true });
|
|
192
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
193
|
+
console.log(JSON.stringify({ status: "applied", manifest: manifestPath, moves: manifest.moves }, null, 2));
|
|
194
|
+
} catch (error) {
|
|
195
|
+
for (const move of [...moves].reverse()) {
|
|
196
|
+
if (existsSync(move.to) && !existsSync(move.from)) await rename(move.to, move.from);
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
} finally {
|
|
200
|
+
if (lock) await lock.close();
|
|
201
|
+
await rm(lockPath, { force: true });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
main().catch((error) => {
|
|
206
|
+
console.error(`Learning relocation failed: ${error.message}`);
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
});
|