@namewta/speculo 1.0.18 → 1.0.20
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 +8 -7
- package/THIRD-PARTY-NOTICES.md +33 -0
- package/dist/src/agent-files.js +2 -2
- package/dist/src/agent-files.js.map +1 -1
- package/dist/src/agent-skills.d.ts +81 -0
- package/dist/src/agent-skills.js +530 -0
- package/dist/src/agent-skills.js.map +1 -0
- package/dist/src/cli.js +11 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +5 -3
- package/dist/src/config.js.map +1 -1
- package/dist/src/doctor.js +7 -2
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +23 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/manifest.d.ts +9 -0
- package/dist/src/manifest.js +45 -7
- package/dist/src/manifest.js.map +1 -1
- package/dist/src/refresh.js +40 -0
- package/dist/src/refresh.js.map +1 -1
- package/dist/src/structured.js +15 -4
- package/dist/src/structured.js.map +1 -1
- package/dist/src/transaction.d.ts +24 -3
- package/dist/src/transaction.js +191 -12
- package/dist/src/transaction.js.map +1 -1
- package/package.json +10 -4
- package/scripts/validate-skills.mjs +56 -0
- package/template/AGENTS.md +1 -1
- package/template/CLAUDE.md +1 -1
- package/template/canonical/canonical-specdev-goal-plan.md +158 -30
- package/template/canonical/canonical-specdev-grill-with-docs.md +155 -33
- package/template/canonical/canonical-specdev-spec.md +162 -161
- package/template/canonical/canonical-specdev-tickets.md +155 -29
- package/template/canonical/canonical-specdev-wayfinder.md +144 -29
- package/template/commands/handoff.md +16 -0
- package/template/commands/retro.md +12 -10
- package/template/skills/archive-and-consolidate/SKILL.md +163 -7
- package/template/skills/archive-and-consolidate/references/cleanup-rules.md +1 -1
- package/template/skills/docs-sync/references/agents/claude-redirect.md +8 -8
- package/template/skills/docs-sync/references/agents/content-contract.md +1 -1
- package/template/skills/docs-sync/references/agents/manifest-discovery.md +7 -7
- package/template/skills/docs-sync/references/agents/role-classification.md +4 -4
- package/template/skills/docs-sync/references/agents-contract.md +1 -1
- package/template/skills/engineering-standards-builder/SKILL.md +149 -9
- package/template/skills/engineering-standards-builder/agents/openai.yaml +2 -0
- package/template/skills/engineering-standards-builder/manifest.txt +2 -1
- package/template/skills/engineering-standards-builder/references/rules/09-mechanical-guardrails.md +9 -0
- package/template/skills/engineering-standards-builder/scripts/validate-builder.mjs +4 -7
- package/template/skills/git-history-squash/SKILL.md +87 -11
- package/template/skills/git-history-squash/agents/openai.yaml +2 -0
- package/template/skills/optimize-codex-config/SKILL.md +71 -11
- package/template/skills/retrospective/SKILL.md +43 -0
- package/template/skills/{speculo-retro → retrospective}/references/friction-taxonomy.md +22 -13
- package/template/skills/{speculo-retro → retrospective}/references/issue-drafting-sop.md +13 -21
- package/template/skills/source-code-zip/SKILL.md +44 -8
- package/template/skills/source-code-zip/references/{entry-procedure.md → cli-reference.md} +0 -43
- package/template/skills/upstream-fork-sync/SKILL.md +71 -8
- package/template/skills/{writing-great-skills → writing-for-agents}/GLOSSARY.md +1 -1
- package/template/skills/writing-for-agents/SKILL.md +22 -0
- package/template/skills/writing-for-agents/agents/openai.yaml +2 -0
- package/template/skills/{writing-great-skills/references/entry-procedure.md → writing-for-agents/references/authoring-principles.md} +2 -9
- package/template/workflows/learning/A-archive/A-archive.md +2 -5
- package/template/workflows/learning/A-assess-and-plan/A-assess-and-plan.md +2 -5
- package/template/workflows/learning/C-consolidate/C-consolidate.md +2 -5
- package/template/workflows/learning/G-goal/G-goal.md +5 -23
- package/template/workflows/learning/H-homework/H-homework.md +2 -5
- package/template/workflows/learning/I-init-setup/I-init-setup.md +4 -9
- package/template/workflows/learning/L-lesson/L-lesson.md +2 -5
- package/template/workflows/learning/Q-question/Q-question.md +16 -8
- package/template/workflows/learning/Q-question/inquiry-template.md +2 -0
- package/template/workflows/learning/Q-question/tools/validate-inquiry.mjs +112 -0
- package/template/workflows/learning/R-review/R-review.md +3 -6
- package/template/workflows/learning/README.md +2 -2
- package/template/workflows/learning/common/rules/knowledge-organization.md +1 -1
- package/template/workflows/learning/common/rules/mastery-policy.md +1 -1
- package/template/workflows/learning/common/skills/socratic-questioning/SKILL.md +1 -1
- package/template/workflows/ops/D-project-deploy/D-project-deploy.md +6 -2
- package/template/workflows/ops/H-host-manage/H-host-manage.md +10 -18
- package/template/workflows/ops/H-host-manage/references/host-profiles.md +21 -0
- package/template/workflows/ops/I-initialize/I-initialize.md +4 -4
- package/template/workflows/ops/README.md +22 -84
- package/template/workflows/ops/S-server-connect/S-server-connect.md +3 -3
- package/template/workflows/ops/V-inventory-view/V-inventory-view.md +2 -2
- package/template/workflows/ops/common/rules/deployment-layout.md +51 -0
- package/template/workflows/ops/common/rules/recovery.md +6 -0
- package/template/workflows/ops/common/rules/server-onboarding.md +6 -0
- package/template/workflows/ops/common/tests/test_ops.mjs +5 -8
- package/template/workflows/ops/common/tests/test_ops_bootstrap.mjs +71 -14
- package/template/workflows/ops/common/tests/test_ops_workspace.mjs +17 -8
- package/template/workflows/ops/common/tools/opslib/bootstrap.mjs +4 -4
- package/template/workflows/ops/common/tools/opslib/transport.mjs +3 -3
- package/template/workflows/person/M-mao-zedong-cognitive-os/M-mao-zedong-cognitive-os.md +13 -68
- package/template/workflows/person/S-steelman-deliberation/S-steelman-deliberation.md +11 -123
- package/template/workflows/person/S-steelman-deliberation/references/state-and-publication.md +86 -0
- package/template/workflows/specdev/A-archive-and-consolidate/A-archive-and-consolidate.md +2 -17
- package/template/workflows/specdev/A-archive-and-consolidate/consolidation-interview.md +13 -4
- package/template/workflows/specdev/C-code-review/C-code-review.md +4 -15
- package/template/workflows/specdev/C-code-review/code-review-template.md +4 -0
- package/template/workflows/specdev/D-diagnose-bugs/D-diagnose-bugs.md +4 -20
- package/template/workflows/specdev/G-grill-with-docs/G-grill-with-docs.md +2 -2
- package/template/workflows/specdev/G-grill-with-docs/references/interview-procedure.md +6 -6
- package/template/workflows/specdev/I-implement/I-implement.md +2 -2
- package/template/workflows/specdev/I-implement/evidence-template.md +8 -0
- package/template/workflows/specdev/I-implement/references/implementation-procedure.md +9 -43
- package/template/workflows/specdev/I-implement/tdd-rules.md +4 -0
- package/template/workflows/specdev/I-init-setup/I-init-setup.md +16 -117
- package/template/workflows/specdev/I-init-setup/config-template.json +5 -1
- package/template/workflows/specdev/I-init-setup/references/first-setup.md +46 -0
- package/template/workflows/specdev/INDEX.md +2 -2
- package/template/workflows/specdev/L-learn-change/L-learn-change.md +11 -89
- package/template/workflows/specdev/L-learn-change/references/lesson-format.md +47 -0
- package/template/workflows/specdev/P-goal-plan/P-goal-plan.md +2 -2
- package/template/workflows/specdev/P-goal-plan/planning-modes.md +1 -1
- package/template/workflows/specdev/P-prototype/P-prototype.md +7 -17
- package/template/workflows/specdev/P-prototype/logic-protocol.md +11 -0
- package/template/workflows/specdev/P-prototype/logic-template.md +27 -0
- package/template/workflows/specdev/P-prototype/project-preview-protocol.md +9 -0
- package/template/workflows/specdev/R-retro/R-retro.md +26 -0
- package/template/workflows/specdev/R-retro/retro-template.md +27 -0
- package/template/workflows/specdev/R-review-architecture/R-review-architecture.md +19 -57
- package/template/workflows/specdev/R-review-architecture/architecture-review-template.md +2 -1
- package/template/workflows/specdev/R-review-architecture/references/selected-design.md +23 -0
- package/template/workflows/specdev/R-review-architecture/review-rubric.md +2 -2
- package/template/workflows/specdev/README.md +17 -6
- package/template/workflows/specdev/S-spec/S-spec.md +13 -136
- package/template/workflows/specdev/T-tickets/T-tickets.md +6 -2
- package/template/workflows/specdev/T-triage/T-triage.md +32 -141
- package/template/workflows/specdev/T-triage/capture-protocol.md +1 -1
- package/template/workflows/specdev/T-triage/ci-security-protocol.md +11 -0
- package/template/workflows/specdev/T-triage/intake-protocol.md +29 -4
- package/template/workflows/specdev/T-triage/pr-delivery-protocol.md +13 -0
- package/template/workflows/specdev/T-triage/pr-record-template.md +39 -0
- package/template/workflows/specdev/T-triage/publish-protocol.md +1 -1
- package/template/workflows/specdev/T-triage/queue-protocol.md +7 -0
- package/template/workflows/specdev/T-triage/reconcile-protocol.md +1 -1
- package/template/workflows/specdev/T-triage/references/issue-pr-policy.md +23 -0
- package/template/workflows/specdev/T-triage/references/release-authentication.md +9 -0
- package/template/workflows/specdev/T-triage/references/release-notes-injection.md +21 -0
- package/template/workflows/specdev/T-triage/references/workflow-yaml-reference.md +37 -0
- package/template/workflows/specdev/T-triage/release-preflight.md +13 -0
- package/template/workflows/specdev/T-triage/release-protocol.md +12 -0
- package/template/workflows/specdev/T-triage/release-recovery.md +18 -0
- package/template/workflows/specdev/T-triage/remote-operations.md +25 -0
- package/template/workflows/specdev/T-triage/scripts/github-transport.mjs +208 -0
- package/template/workflows/specdev/T-triage/source-template.md +7 -0
- package/template/workflows/specdev/T-triage/triage-run-template.md +34 -0
- package/template/workflows/specdev/T-triage/triage-template.md +4 -1
- package/template/workflows/specdev/W-wayfinder/W-wayfinder.md +2 -2
- package/template/workflows/specdev/common/rules/artifact-contract.md +7 -0
- package/template/workflows/specdev/common/rules/change-completion.md +6 -0
- package/template/workflows/specdev/common/rules/deployment-handoff.md +23 -0
- package/template/workflows/specdev/common/rules/workflow-routing.md +6 -0
- package/template/workflows/specdev/common/rules/workflow-state-and-lifecycle.md +1 -1
- package/template/workflows/specdev/common/schemas/config.schema.json +137 -26
- package/template/workflows/specdev/common/schemas/logic-prototype.schema.json +62 -0
- package/template/workflows/specdev/common/schemas/pull-request.schema.json +90 -0
- package/template/workflows/specdev/common/schemas/retrospective.schema.json +58 -0
- package/template/workflows/specdev/common/schemas/triage-run.schema.json +149 -0
- package/template/workflows/specdev/common/schemas/triage.schema.json +117 -15
- package/template/workflows/specdev/common/skills/code-review/SKILL.md +5 -4
- package/template/workflows/specdev/common/skills/code-review/references/result-reuse.md +15 -0
- package/template/workflows/specdev/common/tools/delivery-artifacts.mjs +95 -0
- package/template/workflows/specdev/common/tools/validate-specdev.mjs +60 -19
- package/template/workflows/specdev/runtime-contract.json +4 -2
- package/template/skills/archive-and-consolidate/references/entry-procedure.md +0 -178
- package/template/skills/docs-sync/references/entry-procedure.md +0 -3
- package/template/skills/engineering-standards-builder/references/entry-procedure.md +0 -154
- package/template/skills/git-history-squash/references/entry-procedure.md +0 -94
- package/template/skills/github-npm-ops/SKILL.md +0 -19
- package/template/skills/github-npm-ops/references/ci-and-security-ops.md +0 -178
- package/template/skills/github-npm-ops/references/entry-procedure.md +0 -25
- package/template/skills/github-npm-ops/references/failure-recovery.md +0 -123
- package/template/skills/github-npm-ops/references/issue-pr-triage.md +0 -219
- package/template/skills/github-npm-ops/references/issue-transport.md +0 -26
- package/template/skills/github-npm-ops/references/package-json-checklist.md +0 -171
- package/template/skills/github-npm-ops/references/preflight-checklist.md +0 -39
- package/template/skills/github-npm-ops/references/publish-detection.md +0 -68
- package/template/skills/github-npm-ops/references/release-notes-injection.md +0 -238
- package/template/skills/github-npm-ops/references/release-pipeline.md +0 -108
- package/template/skills/github-npm-ops/references/setup-npm-token.md +0 -63
- package/template/skills/github-npm-ops/references/troubleshooting-playbook.md +0 -293
- package/template/skills/github-npm-ops/references/version-bump-flow.md +0 -217
- package/template/skills/github-npm-ops/references/workflow-yaml-reference.md +0 -268
- package/template/skills/github-npm-ops/scripts/issue-transport.mjs +0 -227
- package/template/skills/optimize-codex-config/references/entry-procedure.md +0 -78
- package/template/skills/speculo-retro/SKILL.md +0 -19
- package/template/skills/speculo-retro/references/entry-procedure.md +0 -23
- package/template/skills/upstream-fork-sync/references/entry-procedure.md +0 -78
- package/template/skills/writing-great-skills/SKILL.md +0 -23
- /package/template/skills/{writing-great-skills → writing-for-agents}/references/document-contract.md +0 -0
- /package/template/workflows/specdev/{G-grill-with-docs → common/rules}/adr-format.md +0 -0
- /package/template/workflows/specdev/{G-grill-with-docs → common/rules}/context-format.md +0 -0
- /package/template/workflows/specdev/{I-implement → common/rules}/design-it-twice.md +0 -0
- /package/template/workflows/specdev/{G-grill-with-docs/domain-modeling-rules.md → common/rules/domain-modeling.md} +0 -0
|
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync, existsSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
6
|
+
import { dirname, join, posix } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import test from "node:test";
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
@@ -18,6 +18,7 @@ import * as model from "../tools/opslib/model.mjs";
|
|
|
18
18
|
const toolsDir = join(dirname(fileURLToPath(import.meta.url)), "../tools");
|
|
19
19
|
const scriptPath = join(toolsDir, "bootstrap-volta.sh");
|
|
20
20
|
const ACK = "I-APPROVE-THIS-BOOTSTRAP";
|
|
21
|
+
const linuxExecution = { skip: process.platform !== "linux" && "Requires Linux /bin/sh and a Linux Node executable; SSH protocol contracts run on every platform" };
|
|
21
22
|
|
|
22
23
|
function sha256(p) {
|
|
23
24
|
return createHash("sha256").update(readFileSync(p)).digest("hex");
|
|
@@ -58,13 +59,14 @@ function pinFor(archives, arch = "linux-x64") {
|
|
|
58
59
|
return { ...base, archives: { ...base.archives, [arch]: slot } };
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function mockTransport({ probeJson = null } = {}) {
|
|
62
|
+
function mockTransport({ probeJson = null, agentJson = null, applyJson = null } = {}) {
|
|
62
63
|
const prev = transport.transportHooks.spawnSync;
|
|
63
64
|
const log = [];
|
|
64
65
|
transport.transportHooks.spawnSync = (cmd, args, kw) => {
|
|
65
66
|
log.push({ cmd, args: [...args], encoding: kw.encoding, inputIsBuffer: Buffer.isBuffer(kw.input) });
|
|
66
67
|
const last = args[args.length - 1];
|
|
67
68
|
if (cmd === "scp") {
|
|
69
|
+
if (applyJson) return { status: 0, stdout: Buffer.alloc(0), stderr: Buffer.alloc(0) };
|
|
68
70
|
const local = args[args.length - 2];
|
|
69
71
|
const spec = args[args.length - 1];
|
|
70
72
|
const remotePath = spec.slice(spec.indexOf(":") + 1);
|
|
@@ -80,11 +82,13 @@ function mockTransport({ probeJson = null } = {}) {
|
|
|
80
82
|
return { status: 0, stdout: Buffer.from(JSON.stringify(probeJson) + "\n"), stderr: Buffer.alloc(0) };
|
|
81
83
|
}
|
|
82
84
|
if (String(last).includes("--input-type=module")) {
|
|
85
|
+
if (agentJson) return { status: 0, stdout: Buffer.from(JSON.stringify({ ok: true, result: agentJson }) + "\n"), stderr: Buffer.alloc(0) };
|
|
83
86
|
const parts = splitRemote(last);
|
|
84
87
|
return spawnSync(parts[0], parts.slice(1), {
|
|
85
88
|
input: kw.input, encoding: "buffer", timeout: kw.timeout, maxBuffer: kw.maxBuffer,
|
|
86
89
|
});
|
|
87
90
|
}
|
|
91
|
+
if (applyJson) return { status: 0, stdout: Buffer.from(String(last).includes("--apply") ? JSON.stringify(applyJson) + "\n" : ""), stderr: Buffer.alloc(0) };
|
|
88
92
|
const parts = splitRemote(last);
|
|
89
93
|
return spawnSync(parts[0], parts.slice(1), {
|
|
90
94
|
input: kw.input, encoding: "buffer", timeout: kw.timeout, maxBuffer: kw.maxBuffer,
|
|
@@ -182,7 +186,20 @@ test("scp uses the same host-key contract", () => {
|
|
|
182
186
|
} finally { transport.transportHooks.spawnSync = prev; rmSync(d, { recursive: true, force: true }); }
|
|
183
187
|
});
|
|
184
188
|
|
|
185
|
-
test("
|
|
189
|
+
test("scp rejects controller paths and unsafe Linux paths before spawning", (t) => {
|
|
190
|
+
const d = mkdtempSync(join(tmpdir(), "ops-scp-path-"));
|
|
191
|
+
t.after(() => rmSync(d, { recursive: true, force: true }));
|
|
192
|
+
const kh = join(d, "known_hosts"), local = join(d, "archive.tar.gz");
|
|
193
|
+
writeFileSync(kh, "fixture pinned key\n"); writeFileSync(local, "reviewed archive");
|
|
194
|
+
const prev = transport.transportHooks.spawnSync;
|
|
195
|
+
t.after(() => { transport.transportHooks.spawnSync = prev; });
|
|
196
|
+
transport.transportHooks.spawnSync = () => { assert.fail("invalid paths must not reach scp"); };
|
|
197
|
+
for (const path of ["C:\\server\\archive.tar.gz", "C:/server/archive.tar.gz", "\\\\server\\share\\archive.tar.gz", "/srv/ops\\archive.tar.gz", "relative.tar.gz", "/srv/../archive.tar.gz", "/srv/space name.tar.gz", "/srv/nul\0.tar.gz"]) {
|
|
198
|
+
assert.throws(() => transport.posixSend({ hostname: "host.invalid", username: "ops", known_hosts: kh }, local, path), /remote scp path/);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("bootstrap-volta apply installs wrapper and does not touch profile", linuxExecution, () => {
|
|
186
203
|
const d = mkdtempSync(join(tmpdir(), "ops-vol-"));
|
|
187
204
|
const home = join(d, "home");
|
|
188
205
|
mkdirSync(home);
|
|
@@ -205,28 +222,33 @@ test("bootstrap-volta apply installs wrapper and does not touch profile", () =>
|
|
|
205
222
|
} finally { rmSync(d, { recursive: true, force: true }); }
|
|
206
223
|
});
|
|
207
224
|
|
|
208
|
-
test("bootstrap-volta apply without ack blocked", () => {
|
|
225
|
+
test("bootstrap-volta apply without ack blocked", linuxExecution, () => {
|
|
209
226
|
const d = mkdtempSync(join(tmpdir(), "ops-ack-"));
|
|
210
227
|
const archives = makeArchives(d);
|
|
211
228
|
const p = spawnSync("/bin/sh", [scriptPath, "--apply", archives.voltaTar, archives.voltaSha, archives.nodeTar, archives.nodeSha, join(d, "volta"), "24.21.0", "nope"], { encoding: "utf8" });
|
|
212
|
-
assert.
|
|
229
|
+
assert.equal(p.error, undefined);
|
|
230
|
+
assert.equal(p.status, 2);
|
|
231
|
+
assert.match(p.stderr, /acknowledgement/);
|
|
213
232
|
rmSync(d, { recursive: true, force: true });
|
|
214
233
|
});
|
|
215
234
|
|
|
216
|
-
test("bootstrap-volta sha mismatch blocked", () => {
|
|
235
|
+
test("bootstrap-volta sha mismatch blocked", linuxExecution, () => {
|
|
217
236
|
const d = mkdtempSync(join(tmpdir(), "ops-sha-"));
|
|
218
237
|
const archives = makeArchives(d);
|
|
219
238
|
const p = spawnSync("/bin/sh", [scriptPath, "--apply", archives.voltaTar, "0".repeat(64), archives.nodeTar, archives.nodeSha, join(d, "volta"), "24.21.0", ACK], { encoding: "utf8" });
|
|
220
|
-
assert.
|
|
239
|
+
assert.equal(p.error, undefined);
|
|
240
|
+
assert.equal(p.status, 2);
|
|
241
|
+
assert.match(p.stderr, /installer changed after review/);
|
|
221
242
|
assert.ok(!existsSync(join(d, "volta", "bin", "ops-node")));
|
|
222
243
|
rmSync(d, { recursive: true, force: true });
|
|
223
244
|
});
|
|
224
245
|
|
|
225
|
-
function cliFixture() {
|
|
246
|
+
function cliFixture({ executeLinux = false } = {}) {
|
|
226
247
|
const tmp = mkdtempSync(join(tmpdir(), "ops-bootcli-"));
|
|
227
248
|
const state = join(tmp, "controller");
|
|
228
|
-
|
|
229
|
-
|
|
249
|
+
// A simulated Linux SSH target is not a path on the controller filesystem.
|
|
250
|
+
const target = executeLinux ? join(tmp, "server") : "/srv/speculo-bootstrap-test";
|
|
251
|
+
if (executeLinux) mkdirSync(target, { recursive: true });
|
|
230
252
|
const kh = join(tmp, "known_hosts");
|
|
231
253
|
writeFileSync(kh, "fixture pinned key\n");
|
|
232
254
|
const prevProbe = cliHooks.probeLocal;
|
|
@@ -251,6 +273,39 @@ const missingProbe = {
|
|
|
251
273
|
node_path: null, node_version: null, node_usable: false, profile_has_volta: false,
|
|
252
274
|
};
|
|
253
275
|
|
|
276
|
+
test("Linux SSH bootstrap and enrollment preserve remote paths on every controller platform", () => {
|
|
277
|
+
const fx = cliFixture();
|
|
278
|
+
const connectionNode = posix.join(fx.target, "_host/toolchains/ops/volta/bin/ops-node");
|
|
279
|
+
const mock = mockTransport({ probeJson: missingProbe, applyJson: { status: "installed", connection_node: connectionNode, profile_unchanged: true } });
|
|
280
|
+
try {
|
|
281
|
+
const host = sshHost({ root: fx.target, kh: fx.kh, node: "node" });
|
|
282
|
+
const conn = writeConn(fx.tmp, host);
|
|
283
|
+
const r = captureMain(["bootstrap-node", "--state", fx.state, "--host-id", host.host_id, "--connection-file", conn, "--apply", "--account", "ops", "--host-root", fx.target, "--ack", ACK,
|
|
284
|
+
"--volta-archive", fx.archives.voltaTar, "--volta-sha256", fx.archives.voltaSha, "--node-archive", fx.archives.nodeTar, "--node-sha256", fx.archives.nodeSha]);
|
|
285
|
+
assert.equal(r.code, 0, r.stderr);
|
|
286
|
+
const boot = JSON.parse(r.stdout), pins = pinFor(fx.archives).archives["linux-x64"];
|
|
287
|
+
const paths = [pins.volta.filename, pins.node.filename].map(name => posix.join(fx.target, "_host/installers", name));
|
|
288
|
+
assert.deepEqual(mock.log.filter(e => e.cmd === "scp").map(e => e.args.at(-1)), paths.map(p => "ops@host.invalid:" + p));
|
|
289
|
+
const applyArgs = splitRemote(mock.log.find(e => e.cmd === "ssh" && e.args.at(-1).includes("--apply")).args.at(-1));
|
|
290
|
+
assert.deepEqual(applyArgs.slice(applyArgs.indexOf("--apply") + 1), [paths[0], fx.archives.voltaSha, paths[1], fx.archives.nodeSha, posix.dirname(posix.dirname(connectionNode)), fx.archives.nodeVersion, ACK]);
|
|
291
|
+
assert.equal(JSON.parse(readFileSync(boot.receipt, "utf8")).connection_node, connectionNode);
|
|
292
|
+
assert.deepEqual(Object.keys(model.load(fx.state).hosts), []);
|
|
293
|
+
mock.restore();
|
|
294
|
+
host.connection.node = connectionNode;
|
|
295
|
+
const enrollFile = join(fx.tmp, "register.json"); writeFileSync(enrollFile, JSON.stringify({ hosts: [host], projects: [] }));
|
|
296
|
+
const inventory = { identity: "a".repeat(64), platform: "linux", tools: { node: { path: connectionNode, version: "v24.21.0" } } };
|
|
297
|
+
const enrolledMock = mockTransport({ probeJson: { ...missingProbe, tools: { ...missingProbe.tools, node: connectionNode }, node_path: connectionNode, node_usable: true }, agentJson: inventory });
|
|
298
|
+
try {
|
|
299
|
+
const enrolled = captureMain(["--state", fx.state, "enroll", "--file", enrollFile]);
|
|
300
|
+
assert.equal(enrolled.code, 0, enrolled.stderr);
|
|
301
|
+
assert.equal(model.load(fx.state).hosts[host.host_id].connection.node, connectionNode);
|
|
302
|
+
const snapshot = JSON.parse(enrolled.stdout).hosts[0].snapshot;
|
|
303
|
+
assert.deepEqual(JSON.parse(readFileSync(snapshot, "utf8")), inventory);
|
|
304
|
+
assert.equal(enrolledMock.log.filter(e => e.args.at(-1).includes("--input-type=module")).length, 2);
|
|
305
|
+
} finally { enrolledMock.restore(); }
|
|
306
|
+
} finally { mock.restore(); fx.cleanup(); }
|
|
307
|
+
});
|
|
308
|
+
|
|
254
309
|
test("discover probe without node returns node-missing", () => {
|
|
255
310
|
const fx = cliFixture();
|
|
256
311
|
const mock = mockTransport({ probeJson: missingProbe });
|
|
@@ -313,6 +368,7 @@ test("bootstrap-node sha mismatch blocked", () => {
|
|
|
313
368
|
"--node-archive", fx.archives.nodeTar, "--node-sha256", fx.archives.nodeSha,
|
|
314
369
|
]);
|
|
315
370
|
assert.equal(r.code, 2);
|
|
371
|
+
assert.match(r.stderr, /sha256 does not match the reviewed pin/i);
|
|
316
372
|
assert.ok(!mock.log.some((e) => String(e.args.at(-1) || "").includes("--apply")));
|
|
317
373
|
} finally { mock.restore(); fx.cleanup(); }
|
|
318
374
|
});
|
|
@@ -362,8 +418,8 @@ test("bootstrap-node skips when node exists", () => {
|
|
|
362
418
|
} finally { mock.restore(); fx.cleanup(); }
|
|
363
419
|
});
|
|
364
420
|
|
|
365
|
-
test("bootstrap-node apply then enroll writes inventory and status", () => {
|
|
366
|
-
const fx = cliFixture();
|
|
421
|
+
test("bootstrap-node apply then enroll writes inventory and status", linuxExecution, () => {
|
|
422
|
+
const fx = cliFixture({ executeLinux: true });
|
|
367
423
|
const home = join(fx.tmp, "home");
|
|
368
424
|
mkdirSync(home);
|
|
369
425
|
writeFileSync(join(home, ".bashrc"), "keep-me\n");
|
|
@@ -435,10 +491,11 @@ test("enroll without node blocked", () => {
|
|
|
435
491
|
test("probe --connection-file still does not imply register", () => {
|
|
436
492
|
const fx = cliFixture();
|
|
437
493
|
const mock = mockTransport({
|
|
438
|
-
probeJson: { ...missingProbe, tools: { ...missingProbe.tools, node:
|
|
494
|
+
probeJson: { ...missingProbe, tools: { ...missingProbe.tools, node: "/usr/bin/node" }, node_path: "/usr/bin/node", node_usable: true },
|
|
495
|
+
agentJson: { identity: "a".repeat(64), platform: "linux" },
|
|
439
496
|
});
|
|
440
497
|
try {
|
|
441
|
-
const host = sshHost({ root: fx.target, kh: fx.kh,
|
|
498
|
+
const host = sshHost({ root: fx.target, kh: fx.kh, identity: "discover" });
|
|
442
499
|
const conn = writeConn(fx.tmp, host);
|
|
443
500
|
const r = captureMain(["probe", "--connection-file", conn]);
|
|
444
501
|
assert.equal(r.code, 0, r.stderr);
|
|
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync, existsSync, statSync, symlinkSync, rmSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { dirname, join, resolve, posix } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
9
|
import { digest, emptyStatus, now, writeJson } from "../tools/opslib/core.mjs";
|
|
@@ -18,7 +18,7 @@ function fixture(t) {
|
|
|
18
18
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
19
19
|
const state = join(root, "state"); mkdirSync(state);
|
|
20
20
|
const s = emptyStatus(); s.controller = { controller_id: "controller-a", state_root: state, created_at: now() };
|
|
21
|
-
for (const id of ["node-a", "node-b"]) s.hosts[id] = { host_id: id, display_name: id, platform: "linux", transport: "local", root: join(root, id), identity: "a".repeat(64), connection: {} };
|
|
21
|
+
for (const id of ["node-a", "node-b"]) s.hosts[id] = { host_id: id, display_name: id, platform: process.platform === "win32" ? "windows" : "linux", transport: "local", root: join(root, id), identity: "a".repeat(64), connection: {} };
|
|
22
22
|
for (const id of ["app-a", "app-b"]) s.projects[id] = { project_id: id, display_name: id, kind: "app", service_type: null, source: { type: "local", location: join(root, "source"), revision: "v1" } };
|
|
23
23
|
writeJson(join(state, "status.json"), s);
|
|
24
24
|
const save = () => writeJson(join(state, "status.json"), s);
|
|
@@ -32,7 +32,13 @@ function request(id = "task-one") {
|
|
|
32
32
|
function inventory(h) {
|
|
33
33
|
return { identity: h.identity, platform: "linux", tools: { node: { version: "v22.22.3" } },
|
|
34
34
|
snapshots: { [h.root]: { kind: "directory" } }, diagnostics: { disks: { [h.root]: { free: 4 * 1024 ** 3 } }, memory: { MemAvailable: 1024 ** 3 } },
|
|
35
|
-
docker_control: { status: "observed", endpoint: "unix:///var/run/docker.sock", data_root: join(h.root, "_runtime/docker"), compose_version: "2.30.0" } };
|
|
35
|
+
docker_control: { status: "observed", endpoint: "unix:///var/run/docker.sock", data_root: posix.join(h.root, "_runtime/docker"), compose_version: "2.30.0" } };
|
|
36
|
+
}
|
|
37
|
+
function linuxServer(f) {
|
|
38
|
+
return Object.assign(f.s.hosts["node-a"], { platform: "linux", root: "/srv/speculo-test/node-a" });
|
|
39
|
+
}
|
|
40
|
+
function directoryLink(target, path) {
|
|
41
|
+
symlinkSync(target, path, process.platform === "win32" ? "junction" : "dir");
|
|
36
42
|
}
|
|
37
43
|
function adapterFor(f, options = {}) {
|
|
38
44
|
const calls = { compile: 0, approve: 0, apply: 0 };
|
|
@@ -76,7 +82,7 @@ test("explicit layer routing distinguishes controller, access, server, project a
|
|
|
76
82
|
test("workspace rejects guessed roots, traversal, symlinks, duplicate JSON keys and orphan deployments", (t) => {
|
|
77
83
|
const f = fixture(t);
|
|
78
84
|
for (const path of [".", "/", f.state + "/../state"]) assert.throws(() => statePath(path));
|
|
79
|
-
const link = join(f.root, "linked");
|
|
85
|
+
const link = join(f.root, "linked"); directoryLink(f.state, link); assert.throws(() => statePath(link), /symlink/);
|
|
80
86
|
writeFileSync(join(f.state, "status.json"), '{"schema_version":3,"schema_version":3}'); assert.throws(() => readRegistry(f.state), /duplicate JSON/);
|
|
81
87
|
f.s.deployments.dangling = { deployment_id: "dangling", host_id: "missing", project_id: "app-a" }; f.save(); assert.throws(() => readRegistry(f.state), /orphan/);
|
|
82
88
|
});
|
|
@@ -90,7 +96,7 @@ test("CLI help works without network, server dependencies or state", () => {
|
|
|
90
96
|
const r = spawnSync(process.execPath, [path, "workspace-help"], { encoding: "utf8" }); assert.equal(r.status, 0, r.stderr); assert.match(r.stdout, /server-check/);
|
|
91
97
|
});
|
|
92
98
|
test("Linux checklist distinguishes missing, failed, profile-ready and stale evidence", (t) => {
|
|
93
|
-
const f = fixture(t), h = f
|
|
99
|
+
const f = fixture(t), h = linuxServer(f), inv = inventory(h), at = new Date("2026-01-01T00:00:00Z");
|
|
94
100
|
assert.equal(readiness(assessServer(h), at.toISOString(), { at }), "unknown");
|
|
95
101
|
assert.equal(readiness(assessServer(h, { profile: "compose", inventory: inv }), at.toISOString(), { at }), "ready");
|
|
96
102
|
assert.equal(readiness(assessServer(h, { profile: "compose", inventory: inv }), at.toISOString(), { at: new Date("2026-01-03T00:00:00Z") }), "stale");
|
|
@@ -100,7 +106,7 @@ test("Linux checklist distinguishes missing, failed, profile-ready and stale evi
|
|
|
100
106
|
}
|
|
101
107
|
});
|
|
102
108
|
test("server check uses pinned public-key-only SSH and records restricted evidence", async (t) => {
|
|
103
|
-
const f = fixture(t), h = f
|
|
109
|
+
const f = fixture(t), h = linuxServer(f), kh = join(f.root, "known_hosts"); writeFileSync(kh, "fixture verified key");
|
|
104
110
|
Object.assign(h, { transport: "ssh", connection: { hostname: "example.invalid", username: "deploy", known_hosts: kh } }); f.save();
|
|
105
111
|
let spawned = 0, called = 0;
|
|
106
112
|
const a = { sshArgv: () => ["ssh", "-o", "StrictHostKeyChecking=yes"], spawn: (_cmd, args) => { spawned++; assert.ok(args.includes("PasswordAuthentication=no")); assert.ok(args.includes("KbdInteractiveAuthentication=no")); return { status: 0 }; }, call: () => { called++; return inventory(h); } };
|
|
@@ -111,7 +117,7 @@ test("server check uses pinned public-key-only SSH and records restricted eviden
|
|
|
111
117
|
writeFileSync(kh, "different key"); assert.notEqual(check.host_digest, targetDigest(h));
|
|
112
118
|
});
|
|
113
119
|
test("failed SSH login never triggers Node probe and is not reported ready", async (t) => {
|
|
114
|
-
const f = fixture(t), h = f
|
|
120
|
+
const f = fixture(t), h = linuxServer(f), kh = join(f.root, "known_hosts"); writeFileSync(kh, "fixture"); Object.assign(h, { transport: "ssh", connection: { hostname: "invalid", username: "deploy", known_hosts: kh } }); f.save();
|
|
115
121
|
const r = await checkServer(f.state, "node-a", {}, { sshArgv: () => ["ssh"], spawn: () => ({ status: 255 }), call: () => { throw new Error("must not probe"); } });
|
|
116
122
|
assert.equal(r.status, "blocked"); assert.equal(r.checks.find(c => c.id === "machine-identity").status, "unknown");
|
|
117
123
|
});
|
|
@@ -210,7 +216,10 @@ test("fleet writes immutable generations, verifies readback and retains one cons
|
|
|
210
216
|
const before = readFileSync(first.markdown, "utf8"); writeFileSync(first.html, "tampered"); assert.throws(() => generateFleet(f.state, { at }), /readback mismatch/); assert.equal(readFileSync(first.markdown, "utf8"), before);
|
|
211
217
|
});
|
|
212
218
|
test("fleet refuses symlink outputs and catalog locks without overwriting old entry", (t) => {
|
|
213
|
-
const f = fixture(t),
|
|
219
|
+
const f = fixture(t), outsideDir = join(f.root, "outside"), outside = join(outsideDir, "sentinel.txt");
|
|
220
|
+
mkdirSync(outsideDir); writeFileSync(outside, "untouched");
|
|
221
|
+
if (process.platform === "win32") directoryLink(outsideDir, join(f.state, "FLEET.md"));
|
|
222
|
+
else symlinkSync(outside, join(f.state, "FLEET.md"));
|
|
214
223
|
assert.throws(() => generateFleet(f.state), /symlink/); assert.equal(readFileSync(outside, "utf8"), "untouched");
|
|
215
224
|
rmSync(join(f.state, "FLEET.md")); mkdirSync(join(f.state, ".locks/catalog"), { recursive: true }); assert.throws(() => generateFleet(f.state), /catalog lock/);
|
|
216
225
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Node-less Linux SSH bootstrap: pinned Volta/Node via POSIX + scp, then enroll. */
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { dirname, basename,
|
|
4
|
+
import { dirname, basename, join, posix as posixPath } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { digest, exact, identifier, newId, noSymlinks, NodeMissing, now, OpsError, privateDir, rootPath, targetJoin, writeJson } from "./core.mjs";
|
|
7
7
|
import { register, validateHost } from "./model.mjs";
|
|
@@ -142,8 +142,8 @@ export function bootstrapNode(args) {
|
|
|
142
142
|
const installers = installerDir(hostRoot);
|
|
143
143
|
const voltaHome = managedVoltaHome(hostRoot, args.account);
|
|
144
144
|
posixCall(endpoint, "#!/bin/sh\nset -eu\nmkdir -p -- \"$1\" \"$2\"\n", { args: [installers, voltaHome] });
|
|
145
|
-
const remoteVolta = join(installers, volta.filename);
|
|
146
|
-
const remoteNode = join(installers, node.filename);
|
|
145
|
+
const remoteVolta = posixPath.join(installers, volta.filename);
|
|
146
|
+
const remoteNode = posixPath.join(installers, node.filename);
|
|
147
147
|
posixSend(endpoint, volta.file, remoteVolta);
|
|
148
148
|
posixSend(endpoint, node.file, remoteNode);
|
|
149
149
|
const applied = posixCall(endpoint, SCRIPT, {
|
|
@@ -181,7 +181,7 @@ export function enroll(state, request) {
|
|
|
181
181
|
if (posix.tools?.node === "missing" || posix.node_usable === false) throw new NodeMissing(posix);
|
|
182
182
|
const discovery = host.identity === "discover" || host.identity == null;
|
|
183
183
|
if (discovery) host.identity = "0".repeat(64);
|
|
184
|
-
if (!isAbsolute(host.connection.node)) {
|
|
184
|
+
if (!posixPath.isAbsolute(host.connection.node) || host.connection.node.includes("\\")) {
|
|
185
185
|
throw new OpsError("connection.node must be the absolute path returned by bootstrap-node");
|
|
186
186
|
}
|
|
187
187
|
validateHost(host);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Strict host-key SSH and subprocess local transport. No passwords in argv. */
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
|
-
import { dirname,
|
|
4
|
+
import { dirname, posix, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { canonical, digest, noSymlinks, OpsError, redact, UnknownResult } from "./core.mjs";
|
|
7
7
|
import { fingerprint } from "./agent.mjs";
|
|
@@ -107,8 +107,8 @@ export function posixSend(endpoint, localFile, remotePath, { timeout = 180 } = {
|
|
|
107
107
|
throw new OpsError("POSIX bootstrap is Linux-only; PowerShell targets still require an existing Node");
|
|
108
108
|
}
|
|
109
109
|
noSymlinks(localFile, { allowMissing: false });
|
|
110
|
-
if (typeof remotePath !== "string" || !isAbsolute(remotePath) || remotePath.split("/").includes("..") || /[\s
|
|
111
|
-
throw new OpsError("remote scp path must be absolute without
|
|
110
|
+
if (typeof remotePath !== "string" || !posix.isAbsolute(remotePath) || remotePath.split("/").includes("..") || /[\s\\\0]/.test(remotePath)) {
|
|
111
|
+
throw new OpsError("remote scp path must be POSIX absolute without whitespace, backslashes, NUL or ..");
|
|
112
112
|
}
|
|
113
113
|
const argv = sshArgv(endpoint, { scp: true });
|
|
114
114
|
argv.push("--", localFile, `${endpoint.username}@${endpoint.hostname}:${remotePath}`);
|
|
@@ -9,67 +9,25 @@ keywords: [毛泽东, 毛选, 矛盾分析, 战略, 组织, 咨询]
|
|
|
9
9
|
|
|
10
10
|
# 毛泽东 · 认知操作系统
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
以特定方法论开展“分析问题—制定战略—组织行动”咨询,产物只写当前 person change。激活时读取 `<Path>{roots.workflows}/person/INDEX.md</Path>` 与 `<Path>{roots.workflows}/person/common/rules/activation-and-memory.md</Path>`;Person 没有 README,不加载其他 phase 或整套书籍。
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## 按阶段推进
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
2. 再读取 `<Path>{roots.workflows}/person/common/rules/activation-and-memory.md</Path>`,按当前分支、状态和关键词定位最小相关工件。
|
|
18
|
-
3. 只在本 Work 明确要求恢复、冲突、执行安全或归档证据时扩展为全量读取;缺少匹配证据或 owner/gateway 时停止受影响分支。
|
|
16
|
+
下表有序且硬依赖:上一阶段产物完成、验证后才进入下一阶段。每个阶段只读取自己的入口,角色、声音、模型和引用纪律由该入口及其参考单点维护。
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
| 阶段 | 进入时读取 | 产物与可判定完成门 |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| 1 激活与问诊 | `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/activate.md</Path>` | `<Path>{roots.state}/person/changes/{change}/problem-statement.md</Path>`:首次激活声明;开口三问中至少追问两题且用户回应;问题类型和主框架确定;六段填实 |
|
|
21
|
+
| 2 诊断分析 | `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/diagnose.md</Path>` | `<Path>{roots.state}/person/changes/{change}/analysis.md</Path>`:至少两个 Module A 模型;必过 A1,二手信息过 A3,多方利益过 A6;每个结论含条件/局限,主要矛盾有论证 |
|
|
22
|
+
| 3 战略制定 | `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/strategize.md</Path>` | `<Path>{roots.state}/person/changes/{change}/strategy.md</Path>`:沿定性→定向→站位→时间→投放→运用展开主框架;必要辅助框架;至少两阶段及明确关键战役 |
|
|
23
|
+
| 4 组织行动 | `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/mobilize.md</Path>` | `<Path>{roots.state}/person/changes/{change}/action-plan.md</Path>`:行动、责任人、期限、完成条件、失败处置与反馈机制明确 |
|
|
24
|
+
| 5 综合交付 | `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/deliver.md</Path>` | `<Path>{roots.state}/person/changes/{change}/consultation-output.md</Path>`:按交付模板整合四阶段,教员第一人称、五拍论证、收尾四动作、反模式检查与内在张力标注;全部原文引用有篇目出处 |
|
|
21
25
|
|
|
26
|
+
每项产物回读后必须无残留 `[TODO:]`;缺少用户回应、阶段证据或原文出处时保留当前阶段,不用后续产物掩盖缺口。
|
|
22
27
|
|
|
23
|
-
##
|
|
28
|
+
## 引用检索
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
委托给 `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/activate.md</Path>`。输出首次激活声明,通过开口三问摸清用户处境,判定问题类型并匹配主框架。
|
|
28
|
-
|
|
29
|
-
产物:`<Path>{roots.state}/person/changes/{change}/problem-statement.md</Path>`
|
|
30
|
-
|
|
31
|
-
**完成标准**:首次激活声明已输出,至少已追问 2 个开口问题且用户已回应,问题类型与主框架已匹配,problem-statement.md 六段均已填写无残留 `[TODO:]`。
|
|
32
|
-
|
|
33
|
-
### 2. 诊断分析
|
|
34
|
-
|
|
35
|
-
依赖步骤 1 的 problem-statement.md。委托给 `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/diagnose.md</Path>`。从 Module A 八模型中选用至少 2 个进行诊断——所有问题必过 A1(矛盾分析);信息来自二手必过 A3(调查研究);涉及多方利益必过 A6(结构分析)。
|
|
36
|
-
|
|
37
|
-
产物:`<Path>{roots.state}/person/changes/{change}/analysis.md</Path>`
|
|
38
|
-
|
|
39
|
-
**完成标准**:已应用至少 2 个 Module A 模型,每个模型结论带条件与局限,主要矛盾已明确写出且带论证,analysis.md 无残留 `[TODO:]`。
|
|
40
|
-
|
|
41
|
-
### 3. 战略制定
|
|
42
|
-
|
|
43
|
-
依赖步骤 2 的 analysis.md。委托给 `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/strategize.md</Path>`。沿元框架流水线(定性→定向→站位→时间→投放→运用)展开主框架,必要时补充辅助框架。
|
|
44
|
-
|
|
45
|
-
产物:`<Path>{roots.state}/person/changes/{change}/strategy.md</Path>`
|
|
46
|
-
|
|
47
|
-
**完成标准**:主框架已选定并充分展开,阶段划分清晰(至少分两步),有明确的关键战役判断,strategy.md 无残留 `[TODO:]`。
|
|
48
|
-
|
|
49
|
-
### 4. 组织行动
|
|
50
|
-
|
|
51
|
-
依赖步骤 3 的 strategy.md。委托给 `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/mobilize.md</Path>`。将战略转化为具体行动方案——谁来干、干什么、什么时候干完、干到什么程度算好、干砸了怎么办。
|
|
52
|
-
|
|
53
|
-
产物:`<Path>{roots.state}/person/changes/{change}/action-plan.md</Path>`
|
|
54
|
-
|
|
55
|
-
**完成标准**:行动、责任与反馈机制已明确,action-plan.md 无残留 `[TODO:]`。
|
|
56
|
-
|
|
57
|
-
### 5. 综合交付
|
|
58
|
-
|
|
59
|
-
依赖前四步全部产物。委托给 `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/deliver.md</Path>`。按模板 `../_templates/mao-consultation-output-template.md` 整合为连贯的教员第一人称咨询输出——五拍论证节奏、收尾四动作、引用纪律、反模式检查和内在张力标注。
|
|
60
|
-
|
|
61
|
-
产物:`<Path>{roots.state}/person/changes/{change}/consultation-output.md</Path>`
|
|
62
|
-
|
|
63
|
-
**完成标准**:已整合四阶段产物为连贯的教员第一人称咨询输出,五拍论证节奏可辨识,收尾四动作齐全,所有毛泽东原文引用均带篇目出处,反模式检查通过,consultation-output.md 无残留 `[TODO:]`。
|
|
64
|
-
|
|
65
|
-
## 依赖关系
|
|
66
|
-
|
|
67
|
-
- 步骤 2「诊断分析」依赖步骤 1「激活与问诊」
|
|
68
|
-
- 步骤 3「战略制定」依赖步骤 2「诊断分析」
|
|
69
|
-
- 步骤 4「组织行动」依赖步骤 3「战略制定」
|
|
70
|
-
- 步骤 5「综合交付」依赖步骤 4「组织行动」
|
|
71
|
-
|
|
72
|
-
所有步骤为硬依赖——前一产物必须完成并验证后才能进入下一步。
|
|
30
|
+
需要引用时,先读当前 phase 的 framework index,再在 `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/references/research/15-quote-bank.md</Path>` 定位篇目编号,最后仅回读匹配的原文。目录映射查询 `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/books/README.md</Path>`;无匹配证据则停止该引用或依赖结论,不扫描整套书籍。
|
|
73
31
|
|
|
74
32
|
## 状态追踪
|
|
75
33
|
|
|
@@ -82,16 +40,3 @@ keywords: [毛泽东, 毛选, 矛盾分析, 战略, 组织, 咨询]
|
|
|
82
40
|
- **`methods_applied`**(数组)—— 组织阶段已应用的 Module C 方法列表
|
|
83
41
|
- **`quotes_cited`**(数组)—— 已引用的毛泽东原话及出处
|
|
84
42
|
- **`consultation_status`**(字符串)—— 当前阶段:`activating` → `diagnosing` → `strategizing` → `mobilizing` → `delivering` → `completed`
|
|
85
|
-
|
|
86
|
-
## 渐进披露
|
|
87
|
-
|
|
88
|
-
角色、声音、模型和引用规则继续由各 phase 文件及其相对引用拥有;未进入 phase 时不加载对应材料。
|
|
89
|
-
|
|
90
|
-
| 文件 | 触发条件 |
|
|
91
|
-
|------|----------|
|
|
92
|
-
| `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/activate.md</Path>` | 步骤 1「激活与问诊」进入时 |
|
|
93
|
-
| `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/diagnose.md</Path>` | 步骤 2「诊断分析」进入时 |
|
|
94
|
-
| `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/strategize.md</Path>` | 步骤 3「战略制定」进入时 |
|
|
95
|
-
| `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/mobilize.md</Path>` | 步骤 4「组织行动」进入时 |
|
|
96
|
-
| `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/deliver.md</Path>` | 步骤 5「综合交付」进入时 |
|
|
97
|
-
| `<Path>{roots.workflows}/person/M-mao-zedong-cognitive-os/books/README.md</Path>` | 需要查原文时——引语库 `references/research/15-quote-bank.md` 映射篇目编号到 books 目录 |
|
|
@@ -9,39 +9,13 @@ keywords: [双向钢人, steelman, 决策, 反谄媚, 关键变量, 明确判断
|
|
|
9
9
|
|
|
10
10
|
# 双向钢人论证
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
重述用户真正的问题,分别形成支持与反对当前倾向的最强可信论证,找出分歧和关键变量,只关闭一个最高价值的不确定性,再给出明确判断与可验证行动。不以虚假平衡、迎合立场或通用建议替代判断。
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
激活时读取 `<Path>{roots.workflows}/person/INDEX.md</Path>` 与 `<Path>{roots.workflows}/person/common/rules/activation-and-memory.md</Path>`;Person 不使用不存在的 README。
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
2. 使用钢人论证法,分别给出支持当前想法与反对它的最强论证;
|
|
18
|
-
3. 找出双方真正的分歧,以及最可能改变结论的关键变量;
|
|
19
|
-
4. 只问用户一个最关键的问题;等用户回答后,再给出明确判断、理由和下一步行动。
|
|
16
|
+
## 输入与权威
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
1. 先读取 `<Path>{roots.workflows}/person/INDEX.md</Path>` 与当前 Work 的状态入口;Person 使用整文件 INDEX,不加载不存在的 README。
|
|
24
|
-
2. 再读取 `<Path>{roots.workflows}/person/common/rules/activation-and-memory.md</Path>`,按当前分支、状态和关键词定位最小相关工件。
|
|
25
|
-
3. 只在本 Work 明确要求恢复、冲突、执行安全或归档证据时扩展为全量读取;缺少匹配证据或 owner/gateway 时停止受影响分支。
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## 输入、权威与边界
|
|
29
|
-
|
|
30
|
-
### 必需输入
|
|
31
|
-
|
|
32
|
-
- 一个需要选择、判断、取舍、排序或制定策略的具体问题。
|
|
33
|
-
|
|
34
|
-
缺失可识别问题,或输入只是无需权衡的事实查询时,返回 `blocked-input`,说明缺少什么,不制造虚假争论。
|
|
35
|
-
|
|
36
|
-
### 可选输入
|
|
37
|
-
|
|
38
|
-
- 用户当前倾向、候选方案与反对意见;
|
|
39
|
-
- 时间窗口、预算、资源、风险和不可逆约束;
|
|
40
|
-
- 已知事实、项目材料、证据或外部来源;
|
|
41
|
-
- 用户已经明确的目标、价值排序和成功标准;
|
|
42
|
-
- 调用方显式提供的 `{change}`。
|
|
43
|
-
|
|
44
|
-
不存在的可选输入静默跳过;已经明确的信息不得重复询问。
|
|
18
|
+
必需一个需要选择、判断、排序或策略的具体问题;缺失或只是无需权衡的事实查询时返回 `blocked-input`,不制造争论。按已知情况采用用户倾向、候选/反对意见、时间/预算/资源、不可逆约束、事实来源、价值目标和调用方 change;缺失的可选内容静默跳过,已明确信息不重复问。
|
|
45
19
|
|
|
46
20
|
### 权威顺序
|
|
47
21
|
|
|
@@ -64,7 +38,9 @@ keywords: [双向钢人, steelman, 决策, 反谄媚, 关键变量, 明确判断
|
|
|
64
38
|
|
|
65
39
|
它不修改其他 person work 的产物或 active 条目,不把运行时状态写回 `{roots.workflows}/person/_state/`,也不提交、推送、发布、部署或执行不可逆操作。
|
|
66
40
|
|
|
67
|
-
##
|
|
41
|
+
## 按持久化阶段执行
|
|
42
|
+
|
|
43
|
+
新建、恢复或状态写入前读取 `<Path>{roots.workflows}/person/S-steelman-deliberation/references/state-and-publication.md</Path>`,遵守 v1 字段、候选校验、原子替换顺序和未知字段保留;不得覆盖其他 Work。
|
|
68
44
|
|
|
69
45
|
### 1. 解析 roots,选择或恢复 change
|
|
70
46
|
|
|
@@ -94,23 +70,9 @@ keywords: [双向钢人, steelman, 决策, 反谄媚, 关键变量, 明确判断
|
|
|
94
70
|
|
|
95
71
|
### 3. 回答前的双向钢人化
|
|
96
72
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
若问题依赖会变化的公开事实、专业事实、冲突材料、代码/测试/配置,或属于医疗、法律、财务、安全等高风险领域,再读取 `<Path>{roots.workflows}/person/S-steelman-deliberation/evidence-gate.md</Path>`;可发现的事实先探索,不拿来问用户。
|
|
100
|
-
|
|
101
|
-
使用 `<Path>{roots.workflows}/person/S-steelman-deliberation/_templates/steelman-dossier-template.md</Path>` 在 change 目录下的唯一临时子目录中写入候选 dossier 与候选 `.status.json`,重读后运行:
|
|
73
|
+
仅在无有效 dossier 时读取 `<Path>{roots.workflows}/person/S-steelman-deliberation/deliberate.md</Path>`,完整重述问题、分层证据、正反钢人、分歧与关键变量,形成唯一关键问题。变化事实、专业事实、冲突材料、代码或高风险领域还须读 `<Path>{roots.workflows}/person/S-steelman-deliberation/evidence-gate.md</Path>`;可发现的事实先探索,不拿来问用户。
|
|
102
74
|
|
|
103
|
-
|
|
104
|
-
node <Path>{roots.workflows}/person/S-steelman-deliberation/tools/validate-steelman-change.mjs</Path> \
|
|
105
|
-
--phase awaiting-answer \
|
|
106
|
-
--change-dir <Path>{roots.state}/person/changes/{change}</Path> \
|
|
107
|
-
--dossier-file <Path>{roots.state}/person/changes/{change}/.steelman-stage/steelman-dossier.md</Path> \
|
|
108
|
-
--status-file <Path>{roots.state}/person/changes/{change}/.steelman-stage/.status.json</Path>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
临时目录名在并发时追加唯一后缀。校验通过后,先原子替换正式 dossier,再原子替换 change `.status.json`;workflow active 只在步骤 4 确认需要等待用户时更新。校验失败时删除临时目录并保持正式工件和旧状态不变。
|
|
112
|
-
|
|
113
|
-
**完成标准**:dossier 是回答前的冻结快照;正反最强可信论证、真正分歧、关键变量和唯一问题均存在;没有最终判断或行动建议;校验退出码为 0。
|
|
75
|
+
按状态与发布合同的“回答前”分支创建、校验并发布 dossier;其中没有最终判断或行动建议,退出码必须为 0。该 dossier 随后冻结。
|
|
114
76
|
|
|
115
77
|
### 4. 只关闭一个最高价值的不确定性
|
|
116
78
|
|
|
@@ -132,73 +94,9 @@ node <Path>{roots.workflows}/person/S-steelman-deliberation/tools/validate-steel
|
|
|
132
94
|
|
|
133
95
|
### 5. 明确裁决并形成行动
|
|
134
96
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
使用 `<Path>{roots.workflows}/person/S-steelman-deliberation/_templates/decision-template.md</Path>` 在唯一临时目录中写入候选 decision 与候选完成态 `.status.json`,随后运行:
|
|
97
|
+
得到有效回答后原样保存,设置 `phase: judging`,再读取 `<Path>{roots.workflows}/person/S-steelman-deliberation/judge.md</Path>`。以冻结 dossier 和真实回答裁决,不重做迎合回答的新分析。
|
|
138
98
|
|
|
139
|
-
|
|
140
|
-
node <Path>{roots.workflows}/person/S-steelman-deliberation/tools/validate-steelman-change.mjs</Path> \
|
|
141
|
-
--phase completed \
|
|
142
|
-
--change-dir <Path>{roots.state}/person/changes/{change}</Path> \
|
|
143
|
-
--decision-file <Path>{roots.state}/person/changes/{change}/.steelman-stage/decision.md</Path> \
|
|
144
|
-
--status-file <Path>{roots.state}/person/changes/{change}/.steelman-stage/.status.json</Path>
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
校验通过后才原子替换正式 decision 和 change 状态,并只从 workflow `status.json.active` 删除匹配当前 `change` 且属于本 work 的条目。校验失败时删除临时目录、返回 `validation-failed`,保留可恢复的 `judging` 状态。
|
|
148
|
-
|
|
149
|
-
**完成标准**:decision 给出明确判断、决定性理由、对最强反方的回应、具体行动、反转条件及置信度;状态与工件一致;校验退出码为 0。
|
|
150
|
-
|
|
151
|
-
## 状态合同
|
|
152
|
-
|
|
153
|
-
### Workflow 状态
|
|
154
|
-
|
|
155
|
-
`<Path>{roots.state}/person/status.json</Path>` 保持 `schema_version: 1`,本 work 只管理 `active` 数组中的如下条目:
|
|
156
|
-
|
|
157
|
-
```json
|
|
158
|
-
{
|
|
159
|
-
"change": "2026-08-18-steelman-company-anniversary",
|
|
160
|
-
"work_id": "person/steelman-deliberation",
|
|
161
|
-
"phase": "awaiting-answer",
|
|
162
|
-
"updated_at": "2026-08-18T00:00:00.000Z"
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
读取—合并—写入时保留未知顶层字段、未知 entry 字段和其他 work 的全部条目。写临时文件、重读 JSON、再原子替换。
|
|
167
|
-
|
|
168
|
-
### Change 状态
|
|
169
|
-
|
|
170
|
-
`<Path>{roots.state}/person/changes/{change}/.status.json</Path>` 至少包含:
|
|
171
|
-
|
|
172
|
-
```json
|
|
173
|
-
{
|
|
174
|
-
"schema_version": 1,
|
|
175
|
-
"artifact": "person-work-status",
|
|
176
|
-
"workflow": "person",
|
|
177
|
-
"change": "2026-08-18-steelman-company-anniversary",
|
|
178
|
-
"work_id": "person/steelman-deliberation",
|
|
179
|
-
"status": "active",
|
|
180
|
-
"phase": "awaiting-answer",
|
|
181
|
-
"key_question": "[唯一关键问题]",
|
|
182
|
-
"key_question_asked": true,
|
|
183
|
-
"question_disposition": "asked",
|
|
184
|
-
"answer_received": false,
|
|
185
|
-
"user_answer": null,
|
|
186
|
-
"created_at": "2026-08-18T00:00:00.000Z",
|
|
187
|
-
"updated_at": "2026-08-18T00:00:00.000Z",
|
|
188
|
-
"completed_at": null,
|
|
189
|
-
"blockers": []
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
允许转换:
|
|
194
|
-
|
|
195
|
-
```text
|
|
196
|
-
intake → deliberating → awaiting-answer → judging → completed
|
|
197
|
-
任一活动阶段 → blocked
|
|
198
|
-
awaiting-answer → reframe-required
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
`question_disposition` 只能是 `asked | already-answered`;前者要求 `key_question_asked: true`,后者要求 `key_question_asked: false`。工件先验证,change 状态后更新,workflow active 最后更新。状态只是工件的索引,不替代工件本身。
|
|
99
|
+
按状态与发布合同的“回答后”分支校验候选 decision 后才发布并关闭本 Work 的 active 条目;失败保留可恢复的 judging,不报完成。decision 必须包含明确判断、决定性理由、最强反方回应、具体行动、反转条件与置信度,且能追溯到关键变量和回答。
|
|
202
100
|
|
|
203
101
|
## 返回合同
|
|
204
102
|
|
|
@@ -213,13 +111,3 @@ awaiting-answer → reframe-required
|
|
|
213
111
|
- 下一路由:等待回答时返回 `<Path>{roots.workflows}/person/S-steelman-deliberation/S-steelman-deliberation.md</Path>` 并携带同一 change;问题已重构时创建新 change;否则明确完成。
|
|
214
112
|
|
|
215
113
|
只有 `completed` 才能声明已经形成最终判断。`awaiting-user-answer` 的下一路由始终是带着同一 change 回到本入口。
|
|
216
|
-
|
|
217
|
-
## 全局完成标准
|
|
218
|
-
|
|
219
|
-
- 用户真正要解决的问题与表层问法已区分;
|
|
220
|
-
- 当前倾向和最强反方都经过可信、可识别的钢人化;
|
|
221
|
-
- 没有编造证据、虚假平衡或因用户立场降低证据标准;
|
|
222
|
-
- 只关闭了一个最高价值的不确定性,且没有重复追问已知信息;
|
|
223
|
-
- 回答前 dossier 保持冻结,回答后 decision 可追溯到关键变量和用户回答;
|
|
224
|
-
- 最终答案不是任何人都能套用的通用建议,而是明确判断与可验证行动;
|
|
225
|
-
- owned 工件、change 状态与 workflow active 一致,验证证据已返回。
|