claude-turing 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-turing",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "type": "module",
5
5
  "description": "Autonomous ML research harness for Claude Code. The autoresearch loop as a formal protocol — iteratively trains, evaluates, and improves ML models with structured experiment tracking, convergence detection, immutable evaluation infrastructure, and safety guardrails.",
6
6
  "bin": {
@@ -49,6 +49,7 @@
49
49
  "node": ">=18.0.0"
50
50
  },
51
51
  "dependencies": {
52
- "commander": "^13.0.0"
52
+ "commander": "^13.0.0",
53
+ "yaml": "^2.6.1"
53
54
  }
54
55
  }
@@ -0,0 +1,151 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import YAML from 'yaml';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const PLUGIN_ROOT = dirname(__dirname);
8
+ const REGISTRY_PATH = join(PLUGIN_ROOT, 'config', 'commands.yaml');
9
+
10
+ const COMMAND_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
11
+ const INVOCATION_MODES = new Set(['slash_only']);
12
+ const MODEL_INVOCATIONS = new Set(['disabled', 'enabled']);
13
+ const SCRIPT_LOCATIONS = new Set(['repo', 'scaffold']);
14
+
15
+ function isRecord(value) {
16
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
17
+ }
18
+
19
+ function requireRecord(value, label) {
20
+ if (!isRecord(value)) {
21
+ throw new Error(`${label} must be a mapping`);
22
+ }
23
+ return value;
24
+ }
25
+
26
+ function requireNonEmptyString(value, label) {
27
+ if (typeof value !== 'string' || value.length === 0) {
28
+ throw new Error(`${label} must be a non-empty string`);
29
+ }
30
+ return value;
31
+ }
32
+
33
+ function requireNonEmptyStringList(value, label) {
34
+ if (!Array.isArray(value) || value.length === 0) {
35
+ throw new Error(`${label} must be a non-empty string list`);
36
+ }
37
+
38
+ for (const [index, item] of value.entries()) {
39
+ requireNonEmptyString(item, `${label}[${index}]`);
40
+ }
41
+
42
+ return value;
43
+ }
44
+
45
+ function requireEnum(value, allowed, label) {
46
+ requireNonEmptyString(value, label);
47
+ if (!allowed.has(value)) {
48
+ throw new Error(`${label} must be one of: ${Array.from(allowed).join(', ')}`);
49
+ }
50
+ return value;
51
+ }
52
+
53
+ function validateEquivalentScript(value, commandName) {
54
+ const label = `commands.${commandName}.equivalent_script`;
55
+ const script = requireRecord(value, label);
56
+ const keys = Object.keys(script).sort();
57
+ const expectedKeys = ['location', 'path'];
58
+ if (keys.length !== expectedKeys.length || keys.some((key, index) => key !== expectedKeys[index])) {
59
+ throw new Error(`${label} must contain exactly: ${expectedKeys.join(', ')}`);
60
+ }
61
+
62
+ requireNonEmptyString(script.path, `${label}.path`);
63
+ requireEnum(
64
+ script.location,
65
+ SCRIPT_LOCATIONS,
66
+ `${label}.location`,
67
+ );
68
+ }
69
+
70
+ function validateCommand(commandName, value) {
71
+ if (!COMMAND_NAME_PATTERN.test(commandName)) {
72
+ throw new Error(`Invalid command name: ${commandName}`);
73
+ }
74
+
75
+ const command = requireRecord(value, `commands.${commandName}`);
76
+ requireNonEmptyString(command.description, `commands.${commandName}.description`);
77
+ requireNonEmptyString(command.lifecycle, `commands.${commandName}.lifecycle`);
78
+ requireEnum(command.invocation_mode, INVOCATION_MODES, `commands.${commandName}.invocation_mode`);
79
+ requireEnum(
80
+ command.model_invocation,
81
+ MODEL_INVOCATIONS,
82
+ `commands.${commandName}.model_invocation`,
83
+ );
84
+
85
+ if (typeof command.mutates_project !== 'boolean') {
86
+ throw new Error(`commands.${commandName}.mutates_project must be a boolean`);
87
+ }
88
+
89
+ requireNonEmptyStringList(command.tools, `commands.${commandName}.tools`);
90
+
91
+ if ('argument_hint' in command) {
92
+ requireNonEmptyString(command.argument_hint, `commands.${commandName}.argument_hint`);
93
+ }
94
+
95
+ if ('equivalent_script' in command) {
96
+ validateEquivalentScript(command.equivalent_script, commandName);
97
+ }
98
+ }
99
+
100
+ function validateRegistry(value) {
101
+ const registry = requireRecord(value, 'Command registry root');
102
+ const configFiles = requireNonEmptyStringList(registry.config_files, 'config_files');
103
+ const commands = requireRecord(registry.commands, 'commands');
104
+
105
+ for (const [commandName, command] of Object.entries(commands)) {
106
+ validateCommand(commandName, command);
107
+ }
108
+
109
+ return {
110
+ commands,
111
+ commandNames: Object.keys(commands).sort(),
112
+ configFiles: [...configFiles].sort(),
113
+ };
114
+ }
115
+
116
+ export async function loadCommandRegistry(registryPath = REGISTRY_PATH) {
117
+ let source;
118
+ try {
119
+ source = await readFile(registryPath, 'utf8');
120
+ } catch (error) {
121
+ throw new Error(`Failed to read command registry at ${registryPath}: ${error.message}`);
122
+ }
123
+
124
+ let parsed;
125
+ try {
126
+ parsed = YAML.parse(source);
127
+ } catch (error) {
128
+ throw new Error(`Failed to parse command registry at ${registryPath}: ${error.message}`);
129
+ }
130
+
131
+ try {
132
+ return validateRegistry(parsed);
133
+ } catch (error) {
134
+ throw new Error(`Invalid command registry at ${registryPath}: ${error.message}`);
135
+ }
136
+ }
137
+
138
+ export async function getCommandNames(registryPath) {
139
+ const registry = await loadCommandRegistry(registryPath);
140
+ return registry.commandNames;
141
+ }
142
+
143
+ export async function getExpectedCommandPaths(registryPath) {
144
+ const names = await getCommandNames(registryPath);
145
+ return ['SKILL.md', ...names.map((name) => `${name}/SKILL.md`)];
146
+ }
147
+
148
+ export async function getConfigFiles(registryPath) {
149
+ const registry = await loadCommandRegistry(registryPath);
150
+ return registry.configFiles;
151
+ }
package/src/install.js CHANGED
@@ -14,43 +14,24 @@ import { join, dirname } from "path";
14
14
  import { fileURLToPath } from "url";
15
15
  import { getTargetPaths } from "./paths.js";
16
16
  import { updateClaudeMd } from "./claude-md.js";
17
+ import { getCommandNames, getConfigFiles } from "./command-registry.js";
17
18
 
18
19
  const __dirname = dirname(fileURLToPath(import.meta.url));
19
20
  const PLUGIN_ROOT = join(__dirname, "..");
20
21
 
21
- // Single source of truth for sub-commands (DRY — used for dirs and file copy)
22
- const SUB_COMMANDS = [
23
- "init", "train", "status", "compare", "sweep", "validate",
24
- "try", "brief", "suggest", "explore", "design", "logbook", "poster",
25
- "report", "mode", "preflight", "card", "seed", "reproduce",
26
- "diagnose", "ablate", "frontier", "profile", "checkpoint", "export",
27
- "lit", "paper", "queue", "retry", "fork",
28
- "diff", "watch", "regress",
29
- "ensemble", "stitch", "warm",
30
- "scale", "budget", "distill",
31
- "transfer", "audit",
32
- "sanity", "baseline", "leak",
33
- "xray", "sensitivity", "calibrate",
34
- "feature", "curriculum",
35
- "prune", "quantize", "merge", "surgery",
36
- "trend", "flashback", "archive", "annotate", "search", "template", "replay",
37
- "cite", "present", "changelog",
38
- "onboard", "share", "review",
39
- "whatif", "counterfactual", "simulate",
40
- "update", "registry",
41
- "postmortem", "doctor", "plan",
42
- ];
43
22
 
44
23
  export async function install(opts = {}) {
45
24
  const scope = opts.global ? "global" : opts.project ? "project" : "global";
46
25
  const paths = getTargetPaths(scope);
26
+ const subCommands = await getCommandNames();
27
+ const configFiles = await getConfigFiles();
47
28
 
48
29
  console.log("Turing ML Research Harness — Installer");
49
30
  console.log(`Target: ${paths.commands} (${scope})`);
50
31
  console.log("");
51
32
 
52
33
  // Create directories for each sub-command + agents + config
53
- for (const subDir of ["", "agents", "config", "rules", "templates", ...SUB_COMMANDS]) {
34
+ for (const subDir of ["", "agents", "config", "rules", "templates", ...subCommands]) {
54
35
  await mkdir(join(paths.commands, subDir), { recursive: true });
55
36
  }
56
37
 
@@ -62,13 +43,13 @@ export async function install(opts = {}) {
62
43
  console.log(" Router -> SKILL.md");
63
44
 
64
45
  // Copy sub-commands as <name>/SKILL.md
65
- for (const cmd of SUB_COMMANDS) {
46
+ for (const cmd of subCommands) {
66
47
  await copyFile(
67
48
  join(PLUGIN_ROOT, "commands", `${cmd}.md`),
68
49
  join(paths.commands, cmd, "SKILL.md"),
69
50
  );
70
51
  }
71
- console.log(` ${SUB_COMMANDS.length} commands installed`);
52
+ console.log(` ${subCommands.length} commands installed`);
72
53
 
73
54
  // Copy rules
74
55
  await copyFile(
@@ -88,20 +69,13 @@ export async function install(opts = {}) {
88
69
  console.log(` ${agentFiles.length} agents installed`);
89
70
 
90
71
  // Copy config (static schema files only)
91
- const CONFIG_FILES = [
92
- "defaults.yaml", "lifecycle.toml", "taxonomy.toml",
93
- "experiment_archetypes.yaml", "novelty_aliases.yaml",
94
- "relationships.toml", "state.toml", "task_taxonomy.yaml",
95
- "failure_modes.yaml",
96
- "watch_alerts.yaml",
97
- ];
98
- for (const file of CONFIG_FILES) {
72
+ for (const file of configFiles) {
99
73
  await copyFile(
100
74
  join(PLUGIN_ROOT, "config", file),
101
75
  join(paths.config, file),
102
76
  );
103
77
  }
104
- console.log(` ${CONFIG_FILES.length} config files installed`);
78
+ console.log(` ${configFiles.length} config files installed`);
105
79
 
106
80
  // Copy templates used by /turing:init
107
81
  await cp(
package/src/verify.js CHANGED
@@ -11,99 +11,14 @@
11
11
  import { access, readdir } from "fs/promises";
12
12
  import { dirname, join } from "path";
13
13
  import { fileURLToPath } from "url";
14
+ import { getConfigFiles, getExpectedCommandPaths } from "./command-registry.js";
14
15
  import { getTargetPaths } from "./paths.js";
15
16
 
16
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
18
  const PLUGIN_ROOT = join(__dirname, "..");
18
19
 
19
- const EXPECTED_COMMANDS = [
20
- "SKILL.md",
21
- "init/SKILL.md",
22
- "train/SKILL.md",
23
- "status/SKILL.md",
24
- "compare/SKILL.md",
25
- "sweep/SKILL.md",
26
- "validate/SKILL.md",
27
- "try/SKILL.md",
28
- "brief/SKILL.md",
29
- "suggest/SKILL.md",
30
- "explore/SKILL.md",
31
- "design/SKILL.md",
32
- "logbook/SKILL.md",
33
- "poster/SKILL.md",
34
- "report/SKILL.md",
35
- "mode/SKILL.md",
36
- "preflight/SKILL.md",
37
- "card/SKILL.md",
38
- "seed/SKILL.md",
39
- "reproduce/SKILL.md",
40
- "diagnose/SKILL.md",
41
- "ablate/SKILL.md",
42
- "frontier/SKILL.md",
43
- "profile/SKILL.md",
44
- "checkpoint/SKILL.md",
45
- "export/SKILL.md",
46
- "lit/SKILL.md",
47
- "paper/SKILL.md",
48
- "queue/SKILL.md",
49
- "retry/SKILL.md",
50
- "fork/SKILL.md",
51
- "diff/SKILL.md",
52
- "watch/SKILL.md",
53
- "regress/SKILL.md",
54
- "ensemble/SKILL.md",
55
- "stitch/SKILL.md",
56
- "warm/SKILL.md",
57
- "scale/SKILL.md",
58
- "budget/SKILL.md",
59
- "distill/SKILL.md",
60
- "transfer/SKILL.md",
61
- "audit/SKILL.md",
62
- "sanity/SKILL.md",
63
- "baseline/SKILL.md",
64
- "leak/SKILL.md",
65
- "xray/SKILL.md",
66
- "sensitivity/SKILL.md",
67
- "calibrate/SKILL.md",
68
- "feature/SKILL.md",
69
- "curriculum/SKILL.md",
70
- "prune/SKILL.md",
71
- "quantize/SKILL.md",
72
- "merge/SKILL.md",
73
- "surgery/SKILL.md",
74
- "trend/SKILL.md",
75
- "flashback/SKILL.md",
76
- "archive/SKILL.md",
77
- "annotate/SKILL.md",
78
- "search/SKILL.md",
79
- "template/SKILL.md",
80
- "replay/SKILL.md",
81
- "cite/SKILL.md",
82
- "present/SKILL.md",
83
- "changelog/SKILL.md",
84
- "onboard/SKILL.md",
85
- "share/SKILL.md",
86
- "review/SKILL.md",
87
- "whatif/SKILL.md",
88
- "counterfactual/SKILL.md",
89
- "simulate/SKILL.md",
90
- "update/SKILL.md",
91
- "registry/SKILL.md",
92
- "postmortem/SKILL.md",
93
- "doctor/SKILL.md",
94
- "plan/SKILL.md",
95
- ];
96
-
97
20
  const EXPECTED_AGENTS = ["ml-researcher.md", "ml-evaluator.md"];
98
21
 
99
- const EXPECTED_CONFIG = [
100
- "defaults.yaml", "lifecycle.toml", "taxonomy.toml",
101
- "experiment_archetypes.yaml", "novelty_aliases.yaml",
102
- "relationships.toml", "state.toml", "task_taxonomy.yaml",
103
- "failure_modes.yaml",
104
- "watch_alerts.yaml",
105
- ];
106
-
107
22
  async function templateFiles(root, relativeDir = "templates") {
108
23
  const dir = join(root, relativeDir);
109
24
  const entries = await readdir(dir, { withFileTypes: true });
@@ -135,6 +50,8 @@ async function fileExists(path) {
135
50
  }
136
51
 
137
52
  export async function verify(opts = {}) {
53
+ const expectedCommands = await getExpectedCommandPaths();
54
+ const expectedConfig = await getConfigFiles();
138
55
  const scopes = opts.scope ? [opts.scope] : ["global", "project"];
139
56
  const expectedTemplates = await templateFiles(PLUGIN_ROOT);
140
57
  let found = false;
@@ -151,7 +68,7 @@ export async function verify(opts = {}) {
151
68
  let missing = 0;
152
69
 
153
70
  console.log("Commands:");
154
- for (const cmd of EXPECTED_COMMANDS) {
71
+ for (const cmd of expectedCommands) {
155
72
  const ok = await fileExists(join(paths.commands, cmd));
156
73
  console.log(` ${ok ? "✓" : "✗"} commands/${cmd}`);
157
74
  if (!ok) missing++;
@@ -165,7 +82,7 @@ export async function verify(opts = {}) {
165
82
  }
166
83
 
167
84
  console.log("\nConfig:");
168
- for (const cfg of EXPECTED_CONFIG) {
85
+ for (const cfg of expectedConfig) {
169
86
  const ok = await fileExists(join(paths.config, cfg));
170
87
  console.log(` ${ok ? "✓" : "✗"} config/${cfg}`);
171
88
  if (!ok) missing++;