thoth-agents 0.3.4 → 0.3.6
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +22 -12
- package/dist/{chunk-W5GCMYA3.js → chunk-HIXSRHEG.js} +295 -66
- package/dist/cli/index.js +37 -4
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/operations/opencode.d.ts +2 -2
- package/dist/cli/owned-skills.d.ts +20 -0
- package/dist/cli/tui/index.js +9 -8
- package/dist/harness/core/owned-skills.d.ts +2 -0
- package/dist/index.js +5 -4
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/skills/thoth-init/SKILL.md +21 -18
- package/plugin/skills/thoth-init/scripts/init.mjs +152 -68
- package/skills/README.md +5 -3
- package/skills/thoth-init/SKILL.md +21 -18
- package/skills/thoth-init/scripts/init.mjs +152 -68
package/dist/cli/index.js
CHANGED
|
@@ -42,8 +42,9 @@ import {
|
|
|
42
42
|
getRequiredSkillInstallCommand,
|
|
43
43
|
installRequiredSkill,
|
|
44
44
|
listOperationHarnesses,
|
|
45
|
-
normalizeEffortSelection
|
|
46
|
-
|
|
45
|
+
normalizeEffortSelection,
|
|
46
|
+
syncOpenCodeOwnedSkills
|
|
47
|
+
} from "../chunk-HIXSRHEG.js";
|
|
47
48
|
import {
|
|
48
49
|
ALL_AGENT_NAMES,
|
|
49
50
|
detectCurrentConfig,
|
|
@@ -551,9 +552,30 @@ function formatConfigSummary(dryRun) {
|
|
|
551
552
|
lines.push(
|
|
552
553
|
` ${DIM}\u25CB thoth-mem remains the owner of hooks, MCP, skill, lifecycle, persistence, receipts, and recovery.${RESET}`
|
|
553
554
|
);
|
|
555
|
+
lines.push(` ${SYMBOLS.check} Global thoth-owned OpenCode skills`);
|
|
554
556
|
lines.push(` ${SYMBOLS.check} Required external skills for this harness`);
|
|
555
557
|
return lines.join("\n");
|
|
556
558
|
}
|
|
559
|
+
function installOwnedSkillsForOpenCode(dryRun, dependencies) {
|
|
560
|
+
const result = syncOpenCodeOwnedSkills({
|
|
561
|
+
dryRun,
|
|
562
|
+
homeDir: dependencies.homeDir ?? homedir(),
|
|
563
|
+
packageRoot: dependencies.opencodeOwnedSkillPackageRoot
|
|
564
|
+
});
|
|
565
|
+
for (const skill of result.skills) {
|
|
566
|
+
printInfo(` - ${skill.name}: ${skill.destinationPath}`);
|
|
567
|
+
}
|
|
568
|
+
if (!result.success) {
|
|
569
|
+
printError(
|
|
570
|
+
`Failed to synchronize global thoth-owned OpenCode skills: ${result.error ?? "unknown error"}`
|
|
571
|
+
);
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
printSuccess(
|
|
575
|
+
dryRun ? "Global thoth-owned OpenCode skills planned" : "Global thoth-owned OpenCode skills synchronized"
|
|
576
|
+
);
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
557
579
|
function installRequiredSkillsForHarness(harness, dryRun, homeDir = homedir()) {
|
|
558
580
|
if (dryRun) {
|
|
559
581
|
printInfo(`Dry run mode - required ${harness} skills:`);
|
|
@@ -617,7 +639,7 @@ async function runInstall(config, dependencies) {
|
|
|
617
639
|
const detected = detectCurrentConfig();
|
|
618
640
|
const isUpdate = detected.isInstalled;
|
|
619
641
|
printHeader(isUpdate);
|
|
620
|
-
const totalSteps =
|
|
642
|
+
const totalSteps = 6;
|
|
621
643
|
let step = 1;
|
|
622
644
|
printStep(step++, totalSteps, "Checking OpenCode installation...");
|
|
623
645
|
if (config.dryRun) {
|
|
@@ -666,8 +688,19 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
666
688
|
return 1;
|
|
667
689
|
}
|
|
668
690
|
}
|
|
691
|
+
printStep(
|
|
692
|
+
step++,
|
|
693
|
+
totalSteps,
|
|
694
|
+
"Synchronizing global thoth-owned OpenCode skills..."
|
|
695
|
+
);
|
|
696
|
+
if (!installOwnedSkillsForOpenCode(config.dryRun, dependencies)) return 1;
|
|
669
697
|
printStep(step++, totalSteps, "Installing required external skills...");
|
|
670
|
-
if (!installRequiredSkillsForHarness(
|
|
698
|
+
if (!installRequiredSkillsForHarness(
|
|
699
|
+
"opencode",
|
|
700
|
+
config.dryRun,
|
|
701
|
+
dependencies.homeDir ?? homedir()
|
|
702
|
+
))
|
|
703
|
+
return 1;
|
|
671
704
|
printStep(step++, totalSteps, "Configuring provider-owned thoth-mem...");
|
|
672
705
|
if (!installThothMemForHarness("opencode", config.dryRun, dependencies)) {
|
|
673
706
|
return 1;
|
package/dist/cli/install.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ThothMemSetupOptions, type ThothMemSetupResult } from './thoth-mem-install';
|
|
2
2
|
import type { InstallArgs, InstallConfig } from './types';
|
|
3
3
|
export interface InstallDependencies {
|
|
4
|
+
homeDir?: string;
|
|
5
|
+
opencodeOwnedSkillPackageRoot?: string;
|
|
4
6
|
runThothMemSetup?: (options: ThothMemSetupOptions) => ThothMemSetupResult;
|
|
5
7
|
}
|
|
6
8
|
export declare function createInstallConfig(args: InstallArgs): InstallConfig;
|
|
@@ -9,7 +9,7 @@ export declare const opencodeOperationAdapter: {
|
|
|
9
9
|
};
|
|
10
10
|
export declare function getOpenCodeStatus(context?: OperationContext, evidence?: ProviderEvidenceInput): HarnessStatusReport;
|
|
11
11
|
export declare function buildOpenCodeUpdatePlan(_context?: OperationContext): OperationPlan;
|
|
12
|
-
export declare function buildOpenCodeSyncPlan(
|
|
13
|
-
export declare function buildOpenCodeInstallPlan(
|
|
12
|
+
export declare function buildOpenCodeSyncPlan(context?: OperationContext): OperationPlan;
|
|
13
|
+
export declare function buildOpenCodeInstallPlan(context?: OperationContext): OperationPlan;
|
|
14
14
|
export declare function buildOpenCodeModelPlan(input: ModelConfigInput, _context?: OperationContext): OperationPlan;
|
|
15
15
|
export declare function applyOpenCodePlan(plan: OperationPlan): OperationApplyResult;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ThothOwnedSkillName } from '../harness/core/owned-skills';
|
|
2
|
+
export interface OpenCodeOwnedSkillSyncOptions {
|
|
3
|
+
dryRun?: boolean;
|
|
4
|
+
homeDir?: string;
|
|
5
|
+
packageRoot?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface OpenCodeOwnedSkillSyncEntry {
|
|
8
|
+
name: ThothOwnedSkillName;
|
|
9
|
+
sourcePath: string;
|
|
10
|
+
destinationPath: string;
|
|
11
|
+
}
|
|
12
|
+
export interface OpenCodeOwnedSkillSyncResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
status: 'planned' | 'installed' | 'failed';
|
|
15
|
+
skills: OpenCodeOwnedSkillSyncEntry[];
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function resolveOwnedSkillPackageRoot(moduleUrl: string): string;
|
|
19
|
+
export declare function getOpenCodeOwnedSkillEntries(options?: Pick<OpenCodeOwnedSkillSyncOptions, 'homeDir' | 'packageRoot'>): OpenCodeOwnedSkillSyncEntry[];
|
|
20
|
+
export declare function syncOpenCodeOwnedSkills(options?: OpenCodeOwnedSkillSyncOptions): OpenCodeOwnedSkillSyncResult;
|
package/dist/cli/tui/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
defaultTuiOperations,
|
|
3
3
|
effortChoicesForModel,
|
|
4
4
|
listOperationHarnesses
|
|
5
|
-
} from "../../chunk-
|
|
5
|
+
} from "../../chunk-HIXSRHEG.js";
|
|
6
6
|
import "../../chunk-F2K4CS5J.js";
|
|
7
7
|
|
|
8
8
|
// src/cli/tui/index.tsx
|
|
@@ -765,10 +765,10 @@ function normalizeSelection(current, items) {
|
|
|
765
765
|
if (items.length === 0) return 0;
|
|
766
766
|
return Math.min(current, items.length - 1);
|
|
767
767
|
}
|
|
768
|
-
function
|
|
769
|
-
return rows.filter((role) => role.dirty).map((role) => {
|
|
768
|
+
function rolesForModelPlan(rows, includeUnchanged = false) {
|
|
769
|
+
return rows.filter((role) => includeUnchanged || role.dirty).map((role) => {
|
|
770
770
|
const effortChanged = role.effort?.kind !== role.currentEffort.kind || role.effort?.kind === "effort" && role.currentEffort.kind === "effort" && role.effort.value !== role.currentEffort.value;
|
|
771
|
-
const includeEffort = effortChanged || role.effort?.kind === "effort";
|
|
771
|
+
const includeEffort = includeUnchanged || effortChanged || role.effort?.kind === "effort";
|
|
772
772
|
return {
|
|
773
773
|
role: role.role,
|
|
774
774
|
model: role.model,
|
|
@@ -899,8 +899,9 @@ function App({
|
|
|
899
899
|
(option) => option.id === modelChoiceSelection.id
|
|
900
900
|
);
|
|
901
901
|
const choiceSelected = selectedModelIndex < 0 ? 0 : selectedModelIndex;
|
|
902
|
-
const dirtyRoles =
|
|
903
|
-
const
|
|
902
|
+
const dirtyRoles = rolesForModelPlan(modelRows);
|
|
903
|
+
const rolesToApply = dirtyRoles.length > 0 ? dirtyRoles : rolesForModelPlan(modelRows, true);
|
|
904
|
+
const modelActions = dirtyRoles.length > 0 ? ["Preview changes", "Apply", "Back"] : ["Apply", "Back"];
|
|
904
905
|
const modelMenuItems = [
|
|
905
906
|
...modelRows.map((role) => ({
|
|
906
907
|
id: role.role,
|
|
@@ -989,7 +990,7 @@ function App({
|
|
|
989
990
|
setView("effortChoice");
|
|
990
991
|
}
|
|
991
992
|
function previewModelChanges(applyImmediately) {
|
|
992
|
-
const nextPlan = operations.modelPlan(modelHarness,
|
|
993
|
+
const nextPlan = operations.modelPlan(modelHarness, rolesToApply);
|
|
993
994
|
setPlan(nextPlan);
|
|
994
995
|
setPreviewAction(applyImmediately ? "apply" : "cancel");
|
|
995
996
|
setPreviewBackView("modelRoles");
|
|
@@ -1237,7 +1238,7 @@ function App({
|
|
|
1237
1238
|
const action = modelActions[modelSelected - modelRows.length];
|
|
1238
1239
|
if (action === "Back") setView("manageHarness");
|
|
1239
1240
|
if (action === "Preview changes") previewModelChanges(false);
|
|
1240
|
-
if (action === "Apply
|
|
1241
|
+
if (action === "Apply") previewModelChanges(true);
|
|
1241
1242
|
}
|
|
1242
1243
|
return;
|
|
1243
1244
|
}
|
package/dist/index.js
CHANGED
|
@@ -42,12 +42,13 @@ function createOpenCodeInitCommand({
|
|
|
42
42
|
subtask: false,
|
|
43
43
|
template: `Initialize this project now using the bundled thoth-init skill.
|
|
44
44
|
|
|
45
|
-
The operation must stay
|
|
45
|
+
The operation must stay scoped to openspec/, idempotent, and offline. Run:
|
|
46
46
|
|
|
47
|
-
node "${scriptPath}" --project "${projectRoot}" --
|
|
47
|
+
node "${scriptPath}" --project "${projectRoot}" --json
|
|
48
48
|
|
|
49
|
-
Inspect the JSON result
|
|
50
|
-
skills or
|
|
49
|
+
Inspect the JSON result and report created, managed, and preserved OpenSpec
|
|
50
|
+
assets. Do not install skills or agents, download dependencies, modify harness
|
|
51
|
+
configuration, or invoke the thoth-agents CLI.
|
|
51
52
|
|
|
52
53
|
Bundled skill contract:
|
|
53
54
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thoth-agents",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Adaptive multi-harness orchestration with seven roles, runtime-autonomous Spec Kit-compatible SDD routes, and external provider boundaries.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thoth-agents",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Bundled Spec Kit-compatible SDD skills and MCP configuration; the seven-role global Codex layer requires thoth-agents CLI setup.",
|
|
5
5
|
"skills": "./skills/",
|
|
6
6
|
"mcpServers": "./codex.mcp.json"
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: thoth-init
|
|
3
|
-
description: Initialize
|
|
3
|
+
description: Initialize or synchronize the minimum project OpenSpec governance structure required by thoth-agents SDD flows.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Thoth Init
|
|
7
7
|
|
|
8
|
-
Initialize the current project from this
|
|
9
|
-
`<skill-dir>` as the directory containing this
|
|
10
|
-
script by absolute path:
|
|
8
|
+
Initialize or synchronize the current project's OpenSpec governance from this
|
|
9
|
+
installed skill bundle. Resolve `<skill-dir>` as the directory containing this
|
|
10
|
+
`SKILL.md`, then run the bundled script by absolute path:
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
|
-
node "<skill-dir>/scripts/init.mjs" --project <project-root> --
|
|
13
|
+
node "<skill-dir>/scripts/init.mjs" --project <project-root> --json
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- Codex init creates project governance only. Codex plugin manifests cannot
|
|
19
|
-
install custom agents or global instructions; first run the mandatory
|
|
20
|
-
thoth-agents CLI setup that manages `~/.codex/agents/`,
|
|
21
|
-
`~/.codex/AGENTS.md`, and `~/.codex/config.toml`.
|
|
22
|
-
- Claude uses auto-discovered plugin agents and owned skills; init creates only
|
|
23
|
-
project governance assets.
|
|
16
|
+
The project root must already exist. The initializer preflights the complete
|
|
17
|
+
target structure before changing it, then ensures these minimum paths exist:
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
- `openspec/changes/archive/`
|
|
20
|
+
- `openspec/specs/`
|
|
21
|
+
- `openspec/memory/`
|
|
22
|
+
- `openspec/templates/`
|
|
23
|
+
- `openspec/.thoth-agents.json`
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
constitution
|
|
25
|
+
Missing constitution and SDD template files are copied from the installed
|
|
26
|
+
bundle. Existing constitution and template files remain byte-for-byte intact;
|
|
27
|
+
the thoth-managed manifest may be normalized to the current contract. Inspect
|
|
28
|
+
the JSON `created`, `managed`, and `preserved` arrays when reporting the result.
|
|
29
|
+
|
|
30
|
+
This operation is offline, idempotent, and harness-neutral. Every write stays
|
|
31
|
+
inside `openspec/`. It never installs or synchronizes skills, agents, plugins,
|
|
32
|
+
harness configuration, external dependencies, or global instruction files;
|
|
33
|
+
those are responsibilities of `npx thoth-agents install`.
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import {
|
|
4
4
|
cpSync,
|
|
5
5
|
existsSync,
|
|
6
|
+
lstatSync,
|
|
6
7
|
mkdirSync,
|
|
7
8
|
readdirSync,
|
|
8
9
|
readFileSync,
|
|
9
|
-
statSync,
|
|
10
10
|
writeFileSync,
|
|
11
11
|
} from 'node:fs';
|
|
12
12
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -15,12 +15,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
15
|
const SCRIPT_PATH = fileURLToPath(import.meta.url);
|
|
16
16
|
const SKILL_ROOT = dirname(dirname(SCRIPT_PATH));
|
|
17
17
|
const BUNDLE_ROOT = dirname(SKILL_ROOT);
|
|
18
|
-
const
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
18
|
+
const OPEN_SPEC_DIRECTORIES = [
|
|
19
|
+
'openspec',
|
|
20
|
+
join('openspec', 'changes'),
|
|
21
|
+
join('openspec', 'changes', 'archive'),
|
|
22
|
+
join('openspec', 'specs'),
|
|
23
|
+
join('openspec', 'memory'),
|
|
24
|
+
join('openspec', 'templates'),
|
|
24
25
|
];
|
|
25
26
|
|
|
26
27
|
function parseArgs(argv) {
|
|
@@ -29,110 +30,193 @@ function parseArgs(argv) {
|
|
|
29
30
|
const argument = argv[index];
|
|
30
31
|
if (argument === '--json') options.json = true;
|
|
31
32
|
else if (argument === '--project') options.project = argv[++index];
|
|
32
|
-
else if (argument === '--harness') options.harness = argv[++index];
|
|
33
33
|
else throw new Error(`Unknown argument: ${argument}`);
|
|
34
34
|
}
|
|
35
35
|
if (!options.project) throw new Error('--project is required');
|
|
36
|
-
if (!['opencode', 'codex', 'claude'].includes(options.harness)) {
|
|
37
|
-
throw new Error('--harness must be opencode, codex, or claude');
|
|
38
|
-
}
|
|
39
36
|
return options;
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
function
|
|
43
|
-
if (!existsSync(
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
function assertDirectory(path, label) {
|
|
40
|
+
if (!existsSync(path)) return;
|
|
41
|
+
const stat = lstatSync(path);
|
|
42
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
43
|
+
throw new Error(`${label} must be a directory: ${path}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertRegularFile(path, label) {
|
|
48
|
+
if (!existsSync(path)) return;
|
|
49
|
+
const stat = lstatSync(path);
|
|
50
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
51
|
+
throw new Error(`${label} must be a regular file: ${path}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function collectFiles(sourceRoot, targetRoot) {
|
|
56
|
+
if (!existsSync(sourceRoot)) {
|
|
57
|
+
throw new Error(`Bundled SDD template directory is missing: ${sourceRoot}`);
|
|
58
|
+
}
|
|
59
|
+
assertDirectory(sourceRoot, 'Bundled template directory');
|
|
60
|
+
const entries = [];
|
|
61
|
+
|
|
62
|
+
function visit(source, target) {
|
|
63
|
+
for (const entry of readdirSync(source, { withFileTypes: true })) {
|
|
64
|
+
const sourcePath = join(source, entry.name);
|
|
65
|
+
const targetPath = join(target, entry.name);
|
|
66
|
+
if (entry.isSymbolicLink()) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Bundled templates cannot contain symlinks: ${sourcePath}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
entries.push({ kind: 'directory', target: targetPath });
|
|
73
|
+
visit(sourcePath, targetPath);
|
|
74
|
+
} else if (entry.isFile()) {
|
|
75
|
+
entries.push({ kind: 'file', source: sourcePath, target: targetPath });
|
|
76
|
+
} else {
|
|
77
|
+
throw new Error(`Unsupported bundled template entry: ${sourcePath}`);
|
|
78
|
+
}
|
|
49
79
|
}
|
|
50
|
-
return;
|
|
51
80
|
}
|
|
81
|
+
|
|
82
|
+
visit(sourceRoot, targetRoot);
|
|
83
|
+
return entries;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function preflight(project) {
|
|
87
|
+
assertDirectory(project, 'Project root');
|
|
88
|
+
if (!existsSync(project)) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`--project must reference an existing project directory: ${project}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const constitutionSource = join(
|
|
95
|
+
BUNDLE_ROOT,
|
|
96
|
+
'thoth-constitution',
|
|
97
|
+
'templates',
|
|
98
|
+
'constitution.md',
|
|
99
|
+
);
|
|
100
|
+
assertRegularFile(constitutionSource, 'Bundled constitution template');
|
|
101
|
+
if (!existsSync(constitutionSource)) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Bundled constitution template is missing: ${constitutionSource}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const templates = collectFiles(
|
|
108
|
+
join(BUNDLE_ROOT, 'thoth-sdd', 'templates'),
|
|
109
|
+
join(project, 'openspec', 'templates'),
|
|
110
|
+
);
|
|
111
|
+
for (const directory of OPEN_SPEC_DIRECTORIES) {
|
|
112
|
+
assertDirectory(join(project, directory), 'OpenSpec path');
|
|
113
|
+
}
|
|
114
|
+
for (const entry of templates) {
|
|
115
|
+
if (entry.kind === 'directory') {
|
|
116
|
+
assertDirectory(entry.target, 'OpenSpec template path');
|
|
117
|
+
} else {
|
|
118
|
+
assertRegularFile(entry.target, 'OpenSpec template path');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const constitutionTarget = join(
|
|
123
|
+
project,
|
|
124
|
+
'openspec',
|
|
125
|
+
'memory',
|
|
126
|
+
'constitution.md',
|
|
127
|
+
);
|
|
128
|
+
const manifestTarget = join(project, 'openspec', '.thoth-agents.json');
|
|
129
|
+
assertRegularFile(constitutionTarget, 'OpenSpec constitution path');
|
|
130
|
+
assertRegularFile(manifestTarget, 'OpenSpec manifest path');
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
constitutionSource,
|
|
134
|
+
constitutionTarget,
|
|
135
|
+
manifestTarget,
|
|
136
|
+
templates,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createDirectory(target, report) {
|
|
141
|
+
if (existsSync(target)) return;
|
|
142
|
+
mkdirSync(target);
|
|
143
|
+
report.created.push(target);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function copyPreservingExisting(source, target, report) {
|
|
52
147
|
if (existsSync(target)) {
|
|
53
|
-
report.
|
|
148
|
+
report.preserved.push(target);
|
|
54
149
|
return;
|
|
55
150
|
}
|
|
56
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
57
151
|
cpSync(source, target, { errorOnExist: true });
|
|
58
152
|
report.created.push(target);
|
|
59
153
|
}
|
|
60
154
|
|
|
61
|
-
function
|
|
155
|
+
function writePreservingExisting(target, content, report) {
|
|
62
156
|
if (existsSync(target)) {
|
|
63
|
-
report.
|
|
157
|
+
report.preserved.push(target);
|
|
64
158
|
return;
|
|
65
159
|
}
|
|
66
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
67
160
|
writeFileSync(target, content);
|
|
68
161
|
report.created.push(target);
|
|
69
162
|
}
|
|
70
163
|
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
readFileSync(template, 'utf8').replaceAll('YYYY-MM-DD', today),
|
|
84
|
-
report,
|
|
85
|
-
);
|
|
164
|
+
function synchronizeManagedFile(target, content, report) {
|
|
165
|
+
if (!existsSync(target)) {
|
|
166
|
+
writeFileSync(target, content);
|
|
167
|
+
report.created.push(target);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (readFileSync(target, 'utf8') === content) {
|
|
171
|
+
report.preserved.push(target);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
writeFileSync(target, content);
|
|
175
|
+
report.managed.push(target);
|
|
86
176
|
}
|
|
87
177
|
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (existsSync(templatesRoot)) {
|
|
92
|
-
for (const entry of readdirSync(templatesRoot)) {
|
|
93
|
-
copyMissing(
|
|
94
|
-
join(templatesRoot, entry),
|
|
95
|
-
join(project, 'openspec', 'templates', entry),
|
|
96
|
-
report,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
178
|
+
function synchronizeOpenSpec(project, assets, report) {
|
|
179
|
+
for (const directory of OPEN_SPEC_DIRECTORIES) {
|
|
180
|
+
createDirectory(join(project, directory), report);
|
|
99
181
|
}
|
|
100
|
-
|
|
101
|
-
|
|
182
|
+
for (const entry of assets.templates) {
|
|
183
|
+
if (entry.kind === 'directory') createDirectory(entry.target, report);
|
|
184
|
+
else copyPreservingExisting(entry.source, entry.target, report);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
188
|
+
writePreservingExisting(
|
|
189
|
+
assets.constitutionTarget,
|
|
190
|
+
readFileSync(assets.constitutionSource, 'utf8').replaceAll(
|
|
191
|
+
'YYYY-MM-DD',
|
|
192
|
+
today,
|
|
193
|
+
),
|
|
194
|
+
report,
|
|
195
|
+
);
|
|
196
|
+
synchronizeManagedFile(
|
|
197
|
+
assets.manifestTarget,
|
|
102
198
|
`${JSON.stringify({ version: 1, initializedBy: 'thoth-agents' }, null, 2)}\n`,
|
|
103
199
|
report,
|
|
104
200
|
);
|
|
105
201
|
}
|
|
106
202
|
|
|
107
|
-
function installOpenCode(project, report) {
|
|
108
|
-
for (const skillName of OWNED_SKILL_NAMES) {
|
|
109
|
-
copyMissing(
|
|
110
|
-
join(BUNDLE_ROOT, skillName),
|
|
111
|
-
join(project, '.agents', 'skills', skillName),
|
|
112
|
-
report,
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
203
|
try {
|
|
118
204
|
const options = parseArgs(process.argv.slice(2));
|
|
119
205
|
const project = resolve(options.project);
|
|
120
|
-
|
|
206
|
+
const assets = preflight(project);
|
|
121
207
|
const report = {
|
|
122
208
|
status: 'ready',
|
|
123
|
-
harness: options.harness,
|
|
124
209
|
project,
|
|
125
210
|
created: [],
|
|
126
211
|
managed: [],
|
|
127
|
-
|
|
212
|
+
preserved: [],
|
|
128
213
|
};
|
|
129
214
|
|
|
130
|
-
|
|
131
|
-
if (options.harness === 'opencode') installOpenCode(project, report);
|
|
215
|
+
synchronizeOpenSpec(project, assets, report);
|
|
132
216
|
|
|
133
217
|
const output = options.json
|
|
134
218
|
? JSON.stringify(report)
|
|
135
|
-
: `thoth-agents
|
|
219
|
+
: `thoth-agents synchronized OpenSpec governance in ${project}`;
|
|
136
220
|
process.stdout.write(`${output}\n`);
|
|
137
221
|
} catch (error) {
|
|
138
222
|
process.stderr.write(
|
package/skills/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This directory is the canonical, versioned source for skills owned by
|
|
4
4
|
thoth-agents. `pnpm run integration:sync` copies those skills into the Codex and
|
|
5
|
-
Claude Code plugins;
|
|
6
|
-
|
|
5
|
+
Claude Code plugins; `npx thoth-agents install --agent=opencode` materializes
|
|
6
|
+
the same packaged skill trees globally under `~/.config/opencode/skills/`.
|
|
7
7
|
|
|
8
8
|
Owned workflow skills are `thoth-init`, `thoth-sdd`, `thoth-constitution`,
|
|
9
9
|
`thoth-archive`, and `plan-reviewer`. The last one implements the optional,
|
|
@@ -11,4 +11,6 @@ user-selected, read-only Oracle review before implementation. Mandatory external
|
|
|
11
11
|
skills (`simplify`, `tdd`,
|
|
12
12
|
`progressive-context-router`, and `architectural-grilling`) are not copied here:
|
|
13
13
|
the thoth-agents installer invokes `npx skills add` against their canonical
|
|
14
|
-
repositories so they retain a single source of truth.
|
|
14
|
+
repositories so they retain a single source of truth. `thoth-init` only
|
|
15
|
+
initializes or synchronizes minimum project `openspec/` governance and never
|
|
16
|
+
installs skills.
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: thoth-init
|
|
3
|
-
description: Initialize
|
|
3
|
+
description: Initialize or synchronize the minimum project OpenSpec governance structure required by thoth-agents SDD flows.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Thoth Init
|
|
7
7
|
|
|
8
|
-
Initialize the current project from this
|
|
9
|
-
`<skill-dir>` as the directory containing this
|
|
10
|
-
script by absolute path:
|
|
8
|
+
Initialize or synchronize the current project's OpenSpec governance from this
|
|
9
|
+
installed skill bundle. Resolve `<skill-dir>` as the directory containing this
|
|
10
|
+
`SKILL.md`, then run the bundled script by absolute path:
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
|
-
node "<skill-dir>/scripts/init.mjs" --project <project-root> --
|
|
13
|
+
node "<skill-dir>/scripts/init.mjs" --project <project-root> --json
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- Codex init creates project governance only. Codex plugin manifests cannot
|
|
19
|
-
install custom agents or global instructions; first run the mandatory
|
|
20
|
-
thoth-agents CLI setup that manages `~/.codex/agents/`,
|
|
21
|
-
`~/.codex/AGENTS.md`, and `~/.codex/config.toml`.
|
|
22
|
-
- Claude uses auto-discovered plugin agents and owned skills; init creates only
|
|
23
|
-
project governance assets.
|
|
16
|
+
The project root must already exist. The initializer preflights the complete
|
|
17
|
+
target structure before changing it, then ensures these minimum paths exist:
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
- `openspec/changes/archive/`
|
|
20
|
+
- `openspec/specs/`
|
|
21
|
+
- `openspec/memory/`
|
|
22
|
+
- `openspec/templates/`
|
|
23
|
+
- `openspec/.thoth-agents.json`
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
constitution
|
|
25
|
+
Missing constitution and SDD template files are copied from the installed
|
|
26
|
+
bundle. Existing constitution and template files remain byte-for-byte intact;
|
|
27
|
+
the thoth-managed manifest may be normalized to the current contract. Inspect
|
|
28
|
+
the JSON `created`, `managed`, and `preserved` arrays when reporting the result.
|
|
29
|
+
|
|
30
|
+
This operation is offline, idempotent, and harness-neutral. Every write stays
|
|
31
|
+
inside `openspec/`. It never installs or synchronizes skills, agents, plugins,
|
|
32
|
+
harness configuration, external dependencies, or global instruction files;
|
|
33
|
+
those are responsibilities of `npx thoth-agents install`.
|