@namewta/speculo 1.0.17 → 1.0.19

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 (88) hide show
  1. package/package.json +5 -2
  2. package/template/canonical/canonical-specdev-goal-plan.md +2 -2
  3. package/template/canonical/canonical-specdev-grill-with-docs.md +2 -2
  4. package/template/canonical/canonical-specdev-spec.md +11 -136
  5. package/template/canonical/canonical-specdev-tickets.md +2 -2
  6. package/template/canonical/canonical-specdev-wayfinder.md +2 -2
  7. package/template/commands/handoff.md +16 -0
  8. package/template/commands/retro.md +1 -1
  9. package/template/skills/github-npm-ops/references/issue-pr-triage.md +8 -0
  10. package/template/skills/speculo-retro/references/entry-procedure.md +19 -3
  11. package/template/skills/speculo-retro/references/issue-drafting-sop.md +3 -1
  12. package/template/workflows/learning/A-archive/A-archive.md +2 -5
  13. package/template/workflows/learning/A-assess-and-plan/A-assess-and-plan.md +2 -5
  14. package/template/workflows/learning/C-consolidate/C-consolidate.md +2 -5
  15. package/template/workflows/learning/G-goal/G-goal.md +5 -23
  16. package/template/workflows/learning/H-homework/H-homework.md +2 -5
  17. package/template/workflows/learning/I-init-setup/I-init-setup.md +4 -9
  18. package/template/workflows/learning/L-lesson/L-lesson.md +2 -5
  19. package/template/workflows/learning/Q-question/Q-question.md +16 -8
  20. package/template/workflows/learning/Q-question/inquiry-template.md +2 -0
  21. package/template/workflows/learning/Q-question/tools/validate-inquiry.mjs +112 -0
  22. package/template/workflows/learning/R-review/R-review.md +3 -6
  23. package/template/workflows/learning/README.md +2 -2
  24. package/template/workflows/learning/common/rules/knowledge-organization.md +1 -1
  25. package/template/workflows/learning/common/rules/mastery-policy.md +1 -1
  26. package/template/workflows/learning/common/skills/socratic-questioning/SKILL.md +1 -1
  27. package/template/workflows/ops/D-project-deploy/D-project-deploy.md +15 -9
  28. package/template/workflows/ops/H-host-manage/H-host-manage.md +14 -21
  29. package/template/workflows/ops/H-host-manage/references/host-profiles.md +21 -0
  30. package/template/workflows/ops/I-initialize/I-initialize.md +7 -7
  31. package/template/workflows/ops/INDEX.md +20 -16
  32. package/template/workflows/ops/README.md +29 -73
  33. package/template/workflows/ops/S-server-connect/S-server-connect.md +28 -0
  34. package/template/workflows/ops/V-inventory-view/V-inventory-view.md +30 -0
  35. package/template/workflows/ops/common/CAPABILITIES.md +22 -15
  36. package/template/workflows/ops/common/WORKSPACE-USAGE.md +78 -0
  37. package/template/workflows/ops/common/examples/task-host.example.json +64 -0
  38. package/template/workflows/ops/common/rules/activation-and-memory.md +5 -3
  39. package/template/workflows/ops/common/rules/deployment-layout.md +51 -0
  40. package/template/workflows/ops/common/rules/inventory-and-records.md +55 -0
  41. package/template/workflows/ops/common/rules/recovery.md +6 -0
  42. package/template/workflows/ops/common/rules/research-and-decisions.md +18 -0
  43. package/template/workflows/ops/common/rules/server-onboarding.md +27 -0
  44. package/template/workflows/ops/common/rules/workspace-and-authorization.md +39 -0
  45. package/template/workflows/ops/common/schemas/fleet-view.schema.json +487 -0
  46. package/template/workflows/ops/common/schemas/server-check.schema.json +95 -0
  47. package/template/workflows/ops/common/schemas/task-request.schema.json +138 -0
  48. package/template/workflows/ops/common/templates/FLEET.html +61 -0
  49. package/template/workflows/ops/common/tests/test_ops_workspace.mjs +246 -0
  50. package/template/workflows/ops/common/tools/ops.mjs +5 -3
  51. package/template/workflows/ops/common/tools/opslib/fleet.mjs +155 -0
  52. package/template/workflows/ops/common/tools/opslib/server_checks.mjs +149 -0
  53. package/template/workflows/ops/common/tools/opslib/tasks.mjs +313 -0
  54. package/template/workflows/ops/common/tools/opslib/workspace.mjs +107 -0
  55. package/template/workflows/ops/common/tools/opslib/workspace_cli.mjs +80 -0
  56. package/template/workflows/ops/common/tools/validate-ops.mjs +11 -8
  57. package/template/workflows/ops/manifest.json +43 -7
  58. package/template/workflows/person/M-mao-zedong-cognitive-os/M-mao-zedong-cognitive-os.md +13 -68
  59. package/template/workflows/person/S-steelman-deliberation/S-steelman-deliberation.md +11 -123
  60. package/template/workflows/person/S-steelman-deliberation/references/state-and-publication.md +86 -0
  61. package/template/workflows/specdev/A-archive-and-consolidate/A-archive-and-consolidate.md +2 -17
  62. package/template/workflows/specdev/A-archive-and-consolidate/consolidation-interview.md +10 -1
  63. package/template/workflows/specdev/C-code-review/C-code-review.md +3 -14
  64. package/template/workflows/specdev/C-code-review/code-review-template.md +4 -0
  65. package/template/workflows/specdev/D-diagnose-bugs/D-diagnose-bugs.md +1 -23
  66. package/template/workflows/specdev/G-grill-with-docs/G-grill-with-docs.md +2 -2
  67. package/template/workflows/specdev/I-implement/I-implement.md +2 -2
  68. package/template/workflows/specdev/I-implement/evidence-template.md +8 -0
  69. package/template/workflows/specdev/I-implement/references/implementation-procedure.md +8 -42
  70. package/template/workflows/specdev/I-init-setup/I-init-setup.md +16 -117
  71. package/template/workflows/specdev/I-init-setup/references/first-setup.md +46 -0
  72. package/template/workflows/specdev/L-learn-change/L-learn-change.md +11 -89
  73. package/template/workflows/specdev/L-learn-change/references/lesson-format.md +47 -0
  74. package/template/workflows/specdev/P-goal-plan/P-goal-plan.md +2 -2
  75. package/template/workflows/specdev/P-prototype/P-prototype.md +2 -16
  76. package/template/workflows/specdev/R-review-architecture/R-review-architecture.md +19 -57
  77. package/template/workflows/specdev/R-review-architecture/architecture-review-template.md +2 -1
  78. package/template/workflows/specdev/R-review-architecture/references/selected-design.md +23 -0
  79. package/template/workflows/specdev/R-review-architecture/review-rubric.md +2 -2
  80. package/template/workflows/specdev/README.md +4 -4
  81. package/template/workflows/specdev/S-spec/S-spec.md +11 -138
  82. package/template/workflows/specdev/T-tickets/T-tickets.md +2 -2
  83. package/template/workflows/specdev/T-triage/T-triage.md +21 -136
  84. package/template/workflows/specdev/T-triage/intake-protocol.md +19 -0
  85. package/template/workflows/specdev/W-wayfinder/W-wayfinder.md +2 -2
  86. package/template/workflows/specdev/common/rules/deployment-handoff.md +23 -0
  87. package/template/workflows/specdev/common/skills/code-review/SKILL.md +5 -4
  88. package/template/workflows/specdev/common/skills/code-review/references/result-reuse.md +15 -0
@@ -0,0 +1,107 @@
1
+ /** Explicit controller/server/project routing and bounded workspace paths. */
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { dirname, isAbsolute, join, relative as pathRelative, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { digest, exact, identifier, noSymlinks, OpsError, readJson } from "./core.mjs";
6
+
7
+ export const WORKS = Object.freeze({
8
+ controller: "I-initialize", access: "S-server-connect", server: "H-host-manage",
9
+ project: "D-project-deploy", inventory: "V-inventory-view",
10
+ });
11
+ const WORKFLOW = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
12
+ export const MAX_JSON_BYTES = 16 * 1024 * 1024;
13
+
14
+ export function statePath(value) {
15
+ if (typeof value !== "string" || !isAbsolute(value) || value.includes("\0") || value.split(/[\\/]/).includes("..")) {
16
+ throw new OpsError("--state must be an explicit absolute path without traversal; never inferred from cwd");
17
+ }
18
+ const state = resolve(value);
19
+ const rel = pathRelative(WORKFLOW, state);
20
+ if (dirname(state) === state || rel === "" || (rel !== ".." && !rel.startsWith(".." + sep) && !isAbsolute(rel))) {
21
+ throw new OpsError("state must be a dedicated runtime directory outside static workflow assets");
22
+ }
23
+ noSymlinks(state, { allowMissing: false });
24
+ if (!statSync(state).isDirectory()) throw new OpsError("state is not a directory");
25
+ return state;
26
+ }
27
+
28
+ export function readBoundedJson(path) {
29
+ noSymlinks(path, { allowMissing: false });
30
+ const st = statSync(path);
31
+ if (!st.isFile() || st.size > MAX_JSON_BYTES) throw new OpsError("JSON file is not regular or exceeds 16 MiB");
32
+ return readJson(path);
33
+ }
34
+
35
+ export function readRegistry(state) {
36
+ statePath(state);
37
+ const value = readBoundedJson(join(state, "status.json"));
38
+ if (value.schema_version !== 3 || value.workflow !== "ops" || !Number.isSafeInteger(value.revision) || value.revision < 0) {
39
+ throw new OpsError("workspace requires OPS status schema v3; legacy state must be imported, not overwritten");
40
+ }
41
+ if (!value.controller || !value.controller.controller_id) throw new OpsError("initialize the controller first");
42
+ identifier(value.controller.controller_id, "controller_id");
43
+ if (typeof value.controller.state_root !== "string" || resolve(value.controller.state_root) !== resolve(state)) throw new OpsError("controller state_root mismatch; do not silently adopt another workspace");
44
+ for (const key of ["hosts", "projects", "deployments", "bindings", "allocations", "releases"]) {
45
+ if (!value[key] || typeof value[key] !== "object" || Array.isArray(value[key])) throw new OpsError(`invalid registry map: ${key}`);
46
+ if (Object.keys(value[key]).length > 10000) throw new OpsError(`registry map exceeds workspace bound: ${key}`);
47
+ for (const id of Object.keys(value[key])) identifier(id, key);
48
+ }
49
+ for (const [id, h] of Object.entries(value.hosts)) {
50
+ if (!h || h.host_id !== id || !["local", "ssh"].includes(h.transport)) throw new OpsError(`invalid host record: ${id}`);
51
+ }
52
+ for (const [id, p] of Object.entries(value.projects)) {
53
+ if (!p || p.project_id !== id) throw new OpsError(`invalid project record: ${id}`);
54
+ }
55
+ for (const [id, d] of Object.entries(value.deployments)) {
56
+ if (!d || d.deployment_id !== id || !Object.hasOwn(value.hosts, d.host_id) || !Object.hasOwn(value.projects, d.project_id)) {
57
+ throw new OpsError(`orphan or invalid deployment: ${id}`);
58
+ }
59
+ }
60
+ return value;
61
+ }
62
+
63
+ export function ids(value, label) {
64
+ if (!Array.isArray(value) || new Set(value).size !== value.length) throw new OpsError(`${label} must be an array of distinct IDs`);
65
+ return value.map((id) => identifier(id, label));
66
+ }
67
+
68
+ export function route(registry, context) {
69
+ exact(context, new Set(["scope", "server_ids", "project_ids"]), new Set(["scope", "server_ids", "project_ids"]), "context");
70
+ if (!Object.hasOwn(WORKS, context.scope)) throw new OpsError("scope must be controller, access, server, project or inventory");
71
+ const servers = ids(context.server_ids, "server_ids");
72
+ const projects = ids(context.project_ids, "project_ids");
73
+ if (context.scope === "controller" && (servers.length || projects.length)) throw new OpsError("controller scope cannot target a server or project; use S/H/D");
74
+ if (["access", "server"].includes(context.scope) && (servers.length !== 1 || projects.length)) throw new OpsError("S/H require exactly one explicit server and no project");
75
+ if (context.scope === "project" && (!servers.length || projects.length !== 1)) throw new OpsError("D requires explicit server(s) and exactly one project; no last-used/cwd fallback");
76
+ for (const id of servers) if (context.scope !== "access" && !Object.hasOwn(registry.hosts, id)) throw new OpsError(`unknown server: ${id}; enroll via S first`);
77
+ for (const id of projects) if (!Object.hasOwn(registry.projects, id)) throw new OpsError(`unknown project: ${id}; explicitly register the project first`);
78
+ return { scope: context.scope, work: WORKS[context.scope], controller_id: registry.controller.controller_id,
79
+ server_ids: servers, project_ids: projects, host_ids: servers, registration_required: servers.filter((id) => !Object.hasOwn(registry.hosts, id)), note: "server maps to Host.host_id; controller is never an implicit local server" };
80
+ }
81
+
82
+ export function catalogUnlocked(state) {
83
+ noSymlinks(join(state, ".locks", "catalog"));
84
+ if (existsSync(join(state, ".locks", "catalog"))) throw new OpsError("catalog lock held; inspect the owner before generating a new snapshot");
85
+ }
86
+
87
+ export function fileDigest(path) {
88
+ noSymlinks(path, { allowMissing: false });
89
+ if (!statSync(path).isFile() || statSync(path).size > MAX_JSON_BYTES) throw new OpsError("file exceeds snapshot bound");
90
+ return digest(readFileSync(path));
91
+ }
92
+
93
+ /** Pin identity and transport, not changeable host_services annotations. */
94
+ export function targetDigest(host) {
95
+ if (!host) throw new OpsError("missing target host");
96
+ const { host_id, root, identity, platform, transport, connection } = host;
97
+ const known_hosts_digest = transport === "ssh" ? fileDigest(connection.known_hosts) : null;
98
+ return digest({ host_id, root, identity, platform, transport, connection, known_hosts_digest });
99
+ }
100
+
101
+ export function containedPath(root, path) {
102
+ if (typeof path !== "string" || !isAbsolute(path) || path.split(/[\\/]/).includes("..")) throw new OpsError("expected an absolute contained artifact path");
103
+ const rel = pathRelative(resolve(root), resolve(path));
104
+ if (!rel || rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) throw new OpsError("artifact path outside workspace");
105
+ noSymlinks(path, { allowMissing: false });
106
+ return resolve(path);
107
+ }
@@ -0,0 +1,80 @@
1
+ /** Additive workspace commands. Legacy CLI semantics remain owned by cli.mjs. */
2
+ import { isAbsolute } from "node:path";
3
+ import { OpsError } from "./core.mjs";
4
+ import { readBoundedJson, readRegistry, route, statePath } from "./workspace.mjs";
5
+ import { checkServer } from "./server_checks.mjs";
6
+ import { generateFleet } from "./fleet.mjs";
7
+ import { authorizeTask, prepareTask, runTask } from "./tasks.mjs";
8
+
9
+ export const COMMANDS = Object.freeze(["workspace-help", "route", "server-check", "fleet", "task-plan", "task-authorize", "task-run"]);
10
+ const options = {
11
+ route: ["scope", "server", "project"],
12
+ "server-check": ["server", "profile", "min-free-mib", "min-memory-mib"],
13
+ fleet: ["stale-hours"], "task-plan": ["file"],
14
+ "task-authorize": ["task", "digest", "by", "statement", "ack-destructive"], "task-run": ["task"], "workspace-help": [],
15
+ };
16
+ export function workspaceCommand(argv) {
17
+ const rest = [...argv];
18
+ for (let i = rest.length - 1; i >= 0; i--) if (rest[i] === "--state") rest.splice(i, 2);
19
+ return COMMANDS.includes(rest[0]) ? rest[0] : null;
20
+ }
21
+ export function parseWorkspaceArgs(argv) {
22
+ const command = workspaceCommand(argv);
23
+ if (!command) throw new OpsError("unknown workspace command");
24
+ const args = [...argv], out = { command };
25
+ args.splice(args.indexOf(command), 1);
26
+ const allowed = new Set(["state", ...options[command]]);
27
+ while (args.length) {
28
+ const flag = args.shift();
29
+ if (!flag.startsWith("--") || !allowed.has(flag.slice(2))) throw new OpsError(`unknown ${command} argument: ${flag}`);
30
+ const key = flag.slice(2);
31
+ if (Object.hasOwn(out, key)) throw new OpsError(`duplicate argument: ${flag}`);
32
+ const value = args.shift();
33
+ if (!value || value.startsWith("--")) throw new OpsError(`${flag} needs a value`);
34
+ out[key] = value;
35
+ }
36
+ if (command !== "workspace-help" && !out.state) throw new OpsError("--state is required and must be absolute");
37
+ return out;
38
+ }
39
+ const required = (args, key) => { if (!args[key]) throw new OpsError(`--${key} is required`); return args[key]; };
40
+ const list = (v) => v ? v.split(",") : [];
41
+ function integer(value, fallback) {
42
+ if (value === undefined) return fallback;
43
+ if (!/^[1-9][0-9]*$/.test(value) || !Number.isSafeInteger(Number(value))) throw new OpsError("threshold must be a positive integer");
44
+ return Number(value);
45
+ }
46
+ export async function main(argv = process.argv.slice(2)) {
47
+ try {
48
+ const args = parseWorkspaceArgs(argv);
49
+ if (args.command === "workspace-help") {
50
+ process.stdout.write(JSON.stringify({ commands: options, usage: "node ops.mjs --state ABSOLUTE_STATE COMMAND [options]",
51
+ workflow: "I controller; S server access; H server baseline; D project deployment; V local inventory",
52
+ task: "task-plan -> record existing explicit user authorization once via task-authorize -> task-run; no per-step reconfirmation; no automatic retry",
53
+ legacy: "All other commands retain the original plan/approve/apply contracts" }, null, 2) + "\n");
54
+ return 0;
55
+ }
56
+ const state = statePath(args.state);
57
+ // Keep the existing complete schema/semantic validator authoritative in the installed runtime.
58
+ const { load } = await import("./model.mjs");
59
+ load(state);
60
+ let result;
61
+ switch (args.command) {
62
+ case "route": result = route(readRegistry(state), { scope: required(args, "scope"), server_ids: list(args.server), project_ids: list(args.project) }); break;
63
+ case "server-check": result = await checkServer(state, required(args, "server"), { profile: args.profile ?? "base",
64
+ minFreeMiB: integer(args["min-free-mib"], 1024), minMemoryMiB: integer(args["min-memory-mib"], 256) }); break;
65
+ case "fleet": result = generateFleet(state, { staleHours: integer(args["stale-hours"], 24) }); break;
66
+ case "task-plan": {
67
+ const file = required(args, "file");
68
+ if (!isAbsolute(file)) throw new OpsError("--file must be an absolute task request path");
69
+ result = await prepareTask(state, readBoundedJson(file)); break;
70
+ }
71
+ case "task-authorize": result = authorizeTask(state, required(args, "task"), required(args, "digest"), required(args, "by"), required(args, "statement"), args["ack-destructive"] ?? null); break;
72
+ case "task-run": result = await runTask(state, required(args, "task")); break;
73
+ }
74
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
75
+ return ["blocked", "failed", "partial", "unknown", "stale", "docs_pending", "interrupted"].includes(result.status) ? 2 : 0;
76
+ } catch (error) {
77
+ process.stderr.write(JSON.stringify({ status: "blocked", error: error.message || String(error) }) + "\n");
78
+ return 2;
79
+ }
80
+ }
@@ -5,10 +5,10 @@ import { dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { spawnSync } from "node:child_process";
7
7
  import { validateStatus } from "./opslib/model.mjs";
8
+ import { WORKS } from "./opslib/workspace.mjs";
8
9
 
9
10
  const tools = dirname(fileURLToPath(import.meta.url));
10
11
  const root = resolve(tools, "../..");
11
- const expected = ["D-project-deploy", "H-host-manage", "I-initialize"];
12
12
  const args = process.argv.slice(2);
13
13
 
14
14
  try {
@@ -16,12 +16,17 @@ try {
16
16
  const works = readdirSync(root, { withFileTypes: true })
17
17
  .filter((x) => x.isDirectory() && !["common", "_state", "__pycache__"].includes(x.name))
18
18
  .map((x) => x.name).sort();
19
- if (JSON.stringify(works) !== JSON.stringify(expected)) throw new Error("OPS must expose exactly three work directories");
19
+ const expected = Object.values(WORKS).sort();
20
+ if (JSON.stringify(works) !== JSON.stringify(expected)) throw new Error("OPS Work directories differ from explicit scope router");
21
+ const manifest = JSON.parse(readFileSync(join(root, "manifest.json"), "utf8"));
22
+ const stages = manifest.stages.map((s) => s.id).sort();
23
+ if (new Set(stages).size !== stages.length || JSON.stringify(stages) !== JSON.stringify(works.map((w) => w.slice(2)).sort())) throw new Error("manifest stages differ from Work entries");
24
+ for (const stage of manifest.stages) for (const id of stage.after) if (!stages.includes(id)) throw new Error("unknown manifest prerequisite " + id);
20
25
  for (const id of works) {
21
26
  const text = readFileSync(join(root, id, id + ".md"), "utf8");
22
- if (!text.includes("type: workflow-entry") || !text.includes("读取范围")) throw new Error("invalid work " + id);
27
+ if (!text.includes("type: workflow-entry") || !text.includes("读取范围") || !text.includes("id: ops/" + id.slice(2))) throw new Error("invalid work " + id);
23
28
  }
24
- for (const file of ["INDEX.md", "README.md", "manifest.json", "runtime-contract.json", "common/USAGE.md", "common/CAPABILITIES.md", "common/tools/opslib/execution.mjs"]) {
29
+ for (const file of ["INDEX.md", "README.md", "manifest.json", "runtime-contract.json", "common/USAGE.md", "common/WORKSPACE-USAGE.md", "common/CAPABILITIES.md", "common/templates/FLEET.html", "common/schemas/fleet-view.schema.json", "common/schemas/task-request.schema.json", "common/schemas/server-check.schema.json", "common/tools/opslib/execution.mjs", "common/tools/opslib/tasks.mjs", "common/tools/opslib/fleet.mjs"]) {
25
30
  if (!existsSync(join(root, file))) throw new Error("missing " + file);
26
31
  }
27
32
  for (const name of readdirSync(join(root, "common/schemas")).filter((x) => x.endsWith(".json"))) {
@@ -30,10 +35,8 @@ try {
30
35
  JSON.parse(readFileSync(join(root, "common/toolchains/volta-linux.json"), "utf8"));
31
36
  validateStatus(JSON.parse(readFileSync(join(root, "_state/status.json"), "utf8")));
32
37
  console.log("resource schema valid");
33
- console.log(JSON.stringify({
34
- valid: true, version: "2.2.0", workers: works, schema_version: 3,
35
- scope: "static and empty-resource-schema check; not live target acceptance",
36
- }, null, 2));
38
+ console.log(JSON.stringify({ valid: true, version: manifest.version, workers: works, schema_version: 3,
39
+ scope: "static and empty-resource-schema check; not live target acceptance" }, null, 2));
37
40
  } else if (args.length === 2 && ["--state-root", "--state"].includes(args[0])) {
38
41
  const p = spawnSync(process.execPath, [join(tools, "ops.mjs"), "--state", resolve(args[1]), "validate"], { encoding: "utf8", timeout: 30000 });
39
42
  if (p.status !== 0) throw new Error(p.stdout + p.stderr);
@@ -22,31 +22,50 @@
22
22
  "risk": "local-reversible",
23
23
  "context_budget": 16000
24
24
  },
25
+ {
26
+ "id": "server-connect",
27
+ "after": [
28
+ "initialize"
29
+ ],
30
+ "inputs": [
31
+ "server",
32
+ "verified-host-key",
33
+ "key-or-agent"
34
+ ],
35
+ "outputs": [
36
+ "server-registration",
37
+ "inventory",
38
+ "server-checks"
39
+ ],
40
+ "risk": "production-critical",
41
+ "context_budget": 20000
42
+ },
25
43
  {
26
44
  "id": "host-manage",
27
45
  "after": [],
28
46
  "inputs": [
29
- "host",
47
+ "server",
30
48
  "spec",
31
- "approval"
49
+ "task-authorization-or-plan-approval"
32
50
  ],
33
51
  "outputs": [
34
52
  "host-run",
35
53
  "verification",
36
- "dual-documents"
54
+ "dual-documents",
55
+ "server-checks"
37
56
  ],
38
57
  "risk": "production-critical",
39
- "context_budget": 22000
58
+ "context_budget": 24000
40
59
  },
41
60
  {
42
61
  "id": "project-deploy",
43
62
  "after": [],
44
63
  "inputs": [
45
64
  "project",
46
- "hosts",
65
+ "explicit-servers",
47
66
  "bindings",
48
67
  "spec",
49
- "approval"
68
+ "task-authorization-or-plan-approval"
50
69
  ],
51
70
  "outputs": [
52
71
  "release",
@@ -54,7 +73,24 @@
54
73
  "dual-documents"
55
74
  ],
56
75
  "risk": "production-critical",
57
- "context_budget": 28000
76
+ "context_budget": 30000
77
+ },
78
+ {
79
+ "id": "inventory-view",
80
+ "after": [],
81
+ "inputs": [
82
+ "resource-state",
83
+ "server-checks",
84
+ "task-summaries"
85
+ ],
86
+ "outputs": [
87
+ "fleet-markdown",
88
+ "fleet-json",
89
+ "fleet-html",
90
+ "view-receipt"
91
+ ],
92
+ "risk": "local-reversible",
93
+ "context_budget": 16000
58
94
  }
59
95
  ]
60
96
  }
@@ -9,67 +9,25 @@ keywords: [毛泽东, 毛选, 矛盾分析, 战略, 组织, 咨询]
9
9
 
10
10
  # 毛泽东 · 认知操作系统
11
11
 
12
- 本入口把"分析问题—制定战略—组织行动"组织为渐进披露的咨询流程。产物写入当前 `person/changes/<change>/`。
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
- 1. 先读取 `<Path>{roots.workflows}/person/INDEX.md</Path>` 与当前 Work 的状态入口;Person 使用整文件 INDEX,不加载不存在的 README。
17
- 2. 再读取 `<Path>{roots.workflows}/person/common/rules/activation-and-memory.md</Path>`,按当前分支、状态和关键词定位最小相关工件。
18
- 3. 只在本 Work 明确要求恢复、冲突、执行安全或归档证据时扩展为全量读取;缺少匹配证据或 owner/gateway 时停止受影响分支。
16
+ 下表有序且硬依赖:上一阶段产物完成、验证后才进入下一阶段。每个阶段只读取自己的入口,角色、声音、模型和引用纪律由该入口及其参考单点维护。
19
17
 
20
- 引用检索固定为:先读取当前 phase 的 framework index,再读取 `references/research/15-quote-bank.md` 定位篇目编号,最后只回读匹配的 `books/src/NNN-*.md`;不扫描整套书籍,无匹配证据时停止引用或结论。
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
- ### 1. 激活与问诊
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
- 本 work 的主导词是**钢人化**。它不负责替用户把既有立场说得更顺耳,也不以“正反都说一点”制造表面平衡;它负责在结论形成前,把用户真正要解决的问题、支持当前倾向的最强可信论证、反对当前倾向的最强可信论证和决定结论的变量同时推进到可检验状态,再只关闭一个最高价值的不确定性,最后承担判断责任。
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
- 1. 用最完整、最有力的方式,重述用户真正想解决的问题;
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
- 仅在没有有效 dossier 时读取 `<Path>{roots.workflows}/person/S-steelman-deliberation/deliberate.md</Path>`,按其顺序完成真实问题重述、证据分层、双向钢人、分歧与关键变量识别,并形成唯一关键问题。
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
- ```bash
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
- 关键问题得到有效回答后,将回答原文写入 change `.status.json`,设置 `phase: judging`,再读取 `<Path>{roots.workflows}/person/S-steelman-deliberation/judge.md</Path>`。必须基于冻结 dossier 和用户回答裁决,不重做一份迎合回答的新钢人分析。
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
- ```bash
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 一致,验证证据已返回。