@tmddev/tmd 0.3.0 → 0.3.2
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 +56 -215
- package/dist/cli.js +292 -6
- package/dist/commands/config.d.ts +11 -0
- package/dist/commands/config.js +199 -0
- package/dist/commands/feishu.d.ts +13 -0
- package/dist/commands/feishu.js +388 -0
- package/dist/commands/knowledge.d.ts +8 -0
- package/dist/commands/knowledge.js +117 -0
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +140 -19
- package/dist/commands/server.d.ts +7 -0
- package/dist/commands/server.js +38 -0
- package/dist/commands/skills.js +116 -30
- package/dist/commands/spec.d.ts +19 -0
- package/dist/commands/spec.js +73 -0
- package/dist/commands/ui.d.ts +2 -0
- package/dist/commands/ui.js +33 -0
- package/dist/commands/validate.js +31 -0
- package/dist/tmd-config +0 -0
- package/dist/tmd-feishu +0 -0
- package/dist/tmd-knowledge +0 -0
- package/dist/tmd-plan-gen +0 -0
- package/dist/tmd-server +0 -0
- package/dist/tmd-skills +0 -0
- package/dist/tmd-spec +0 -0
- package/dist/types.d.ts +8 -2
- package/dist/utils/execution-plan.d.ts +56 -0
- package/dist/utils/execution-plan.js +109 -0
- package/dist/utils/llm-plan.d.ts +38 -0
- package/dist/utils/llm-plan.js +159 -0
- package/dist/utils/openspec.js +2 -2
- package/dist/utils/paths.d.ts +5 -0
- package/dist/utils/paths.js +11 -0
- package/dist/utils/skills.js +1 -2
- package/dist/utils/task-status.d.ts +1 -0
- package/dist/utils/task-status.js +11 -0
- package/package.json +3 -4
- package/dist/utils/skillssh.d.ts +0 -12
- package/dist/utils/skillssh.js +0 -147
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import { ensureProjectDirs } from '../utils/paths.js';
|
|
6
|
+
const DESKTOP_NOT_FOUND = 'Desktop app not found. Build with: pnpm --filter tmd-desktop tauri build';
|
|
7
|
+
export function uiCommand() {
|
|
8
|
+
const projectRoot = process.cwd();
|
|
9
|
+
ensureProjectDirs(projectRoot);
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const distCommands = dirname(__filename);
|
|
12
|
+
const distDir = dirname(distCommands);
|
|
13
|
+
const repoRoot = dirname(distDir);
|
|
14
|
+
const appsDesktop = join(repoRoot, 'apps', 'desktop');
|
|
15
|
+
if (!existsSync(join(appsDesktop, 'package.json'))) {
|
|
16
|
+
console.error(DESKTOP_NOT_FOUND);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
// Prefer: pnpm exec tauri dev in apps/desktop with TMD_PROJECT_ROOT
|
|
20
|
+
const env = { ...process.env, TMD_PROJECT_ROOT: projectRoot };
|
|
21
|
+
const r = spawnSync('pnpm', ['exec', 'tauri', 'dev'], {
|
|
22
|
+
cwd: appsDesktop,
|
|
23
|
+
env,
|
|
24
|
+
stdio: 'inherit',
|
|
25
|
+
});
|
|
26
|
+
if (r.error) {
|
|
27
|
+
console.error(DESKTOP_NOT_FOUND);
|
|
28
|
+
console.error(r.error.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
process.exit(r.status ?? 1);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import yaml from 'js-yaml';
|
|
5
|
+
import { validateExecutionPlan } from '../utils/execution-plan.js';
|
|
5
6
|
import { getPlanDir, getDoDir, getCheckDir, getActDir, getSchemasDir, getTmdDir, } from '../utils/paths.js';
|
|
6
7
|
import { getTaskStatus } from '../utils/task-status.js';
|
|
7
8
|
function getBase() {
|
|
@@ -10,6 +11,7 @@ function getBase() {
|
|
|
10
11
|
function validateTask(taskId, base, strict) {
|
|
11
12
|
const issues = [];
|
|
12
13
|
const planPath = join(base, getPlanDir(taskId), 'plan.md');
|
|
14
|
+
const execPlanPath = join(base, getPlanDir(taskId), 'execution-plan.json');
|
|
13
15
|
if (!existsSync(planPath)) {
|
|
14
16
|
issues.push({ level: 'error', path: 'plan.md', message: 'plan.md not found' });
|
|
15
17
|
return { taskId, valid: false, issues };
|
|
@@ -49,6 +51,35 @@ function validateTask(taskId, base, strict) {
|
|
|
49
51
|
issues.push({ level: 'warning', path: 'plan.md', message: 'Tasks section has no checklist items' });
|
|
50
52
|
}
|
|
51
53
|
}
|
|
54
|
+
// Execution plan dependency validation (strict mode)
|
|
55
|
+
if (existsSync(execPlanPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const raw = readFileSync(execPlanPath, 'utf-8');
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
const execIssues = validateExecutionPlan(parsed);
|
|
60
|
+
for (const it of execIssues) {
|
|
61
|
+
issues.push({
|
|
62
|
+
level: it.level === 'error' ? 'error' : 'warning',
|
|
63
|
+
path: 'execution-plan.json',
|
|
64
|
+
message: it.message,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
issues.push({
|
|
70
|
+
level: 'error',
|
|
71
|
+
path: 'execution-plan.json',
|
|
72
|
+
message: `Failed to parse execution-plan.json: ${e.message}`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
issues.push({
|
|
78
|
+
level: 'warning',
|
|
79
|
+
path: 'execution-plan.json',
|
|
80
|
+
message: 'execution-plan.json not found (no execution plan artifact)',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
52
83
|
}
|
|
53
84
|
const hasError = issues.some((i) => i.level === 'error');
|
|
54
85
|
return { taskId, valid: !hasError, issues };
|
package/dist/tmd-config
ADDED
|
Binary file
|
package/dist/tmd-feishu
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/tmd-server
ADDED
|
Binary file
|
package/dist/tmd-skills
CHANGED
|
Binary file
|
package/dist/tmd-spec
ADDED
|
Binary file
|
package/dist/types.d.ts
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Shared type definitions for TMD CLI
|
|
3
3
|
*/
|
|
4
4
|
export interface PlanCommandOptions {
|
|
5
|
-
|
|
5
|
+
agentId?: string | undefined;
|
|
6
6
|
skill?: boolean;
|
|
7
7
|
useSkill?: string;
|
|
8
8
|
validateTasks?: boolean;
|
|
9
|
+
strictDeps?: boolean;
|
|
10
|
+
assignAgent?: string;
|
|
9
11
|
}
|
|
10
12
|
export interface DoCommandOptions {
|
|
11
13
|
data?: string;
|
|
@@ -35,6 +37,10 @@ export interface ListCommandOptions {
|
|
|
35
37
|
export interface SkillsCommandOptions {
|
|
36
38
|
input?: string;
|
|
37
39
|
skill?: string;
|
|
40
|
+
agent?: string[];
|
|
41
|
+
global?: boolean;
|
|
42
|
+
yes?: boolean;
|
|
43
|
+
list?: boolean;
|
|
38
44
|
description?: string;
|
|
39
45
|
tags?: string;
|
|
40
46
|
tag?: string[];
|
|
@@ -57,7 +63,7 @@ export interface SkillMetadata {
|
|
|
57
63
|
version?: string;
|
|
58
64
|
description?: string;
|
|
59
65
|
tags?: string[];
|
|
60
|
-
source?: '
|
|
66
|
+
source?: 'local' | 'github' | 'zip' | 'skills-cli';
|
|
61
67
|
repo?: string;
|
|
62
68
|
branch?: string;
|
|
63
69
|
importedAt?: string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type ExecutionPlanNodeStatus = 'pending' | 'running' | 'done' | 'failed';
|
|
2
|
+
export interface ExecutionPlanNode {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
status?: ExecutionPlanNodeStatus;
|
|
6
|
+
agentId?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ExecutionPlanEdge {
|
|
9
|
+
id: string;
|
|
10
|
+
source: string;
|
|
11
|
+
target: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ExecutionPlan {
|
|
14
|
+
version: string;
|
|
15
|
+
taskId: string;
|
|
16
|
+
nodes: ExecutionPlanNode[];
|
|
17
|
+
edges: ExecutionPlanEdge[];
|
|
18
|
+
meta?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export interface ExecutionPlanValidationIssue {
|
|
21
|
+
level: 'error' | 'warning';
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildExecutionPlan(opts: {
|
|
25
|
+
taskId: string;
|
|
26
|
+
nodes: Array<{
|
|
27
|
+
id: string;
|
|
28
|
+
title: string;
|
|
29
|
+
agentId?: string;
|
|
30
|
+
}>;
|
|
31
|
+
edges: Array<{
|
|
32
|
+
source: string;
|
|
33
|
+
target: string;
|
|
34
|
+
}>;
|
|
35
|
+
version?: string;
|
|
36
|
+
meta?: Record<string, unknown>;
|
|
37
|
+
}): ExecutionPlan;
|
|
38
|
+
/**
|
|
39
|
+
* Convert tmd-plan generated tasks into an execution plan.
|
|
40
|
+
*
|
|
41
|
+
* Note:
|
|
42
|
+
* - The Rust generator may emit `dependencies` as 0-based task indexes.
|
|
43
|
+
* - In the persisted execution plan artifact, task dependencies are represented
|
|
44
|
+
* ONLY by `edges` (source -> target).
|
|
45
|
+
*/
|
|
46
|
+
export declare function executionPlanFromGeneratedTasks(opts: {
|
|
47
|
+
taskId: string;
|
|
48
|
+
tasks: Array<{
|
|
49
|
+
title: string;
|
|
50
|
+
dependencies?: number[] | undefined;
|
|
51
|
+
}>;
|
|
52
|
+
defaultAgentId?: string;
|
|
53
|
+
generatedBy?: string;
|
|
54
|
+
}): ExecutionPlan;
|
|
55
|
+
export declare function validateExecutionPlan(plan: ExecutionPlan): ExecutionPlanValidationIssue[];
|
|
56
|
+
//# sourceMappingURL=execution-plan.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export function buildExecutionPlan(opts) {
|
|
2
|
+
const version = opts.version ?? '1';
|
|
3
|
+
const nodes = opts.nodes.map((n) => {
|
|
4
|
+
const node = {
|
|
5
|
+
id: n.id,
|
|
6
|
+
title: n.title,
|
|
7
|
+
status: 'pending',
|
|
8
|
+
};
|
|
9
|
+
if (n.agentId !== undefined)
|
|
10
|
+
node.agentId = n.agentId;
|
|
11
|
+
return node;
|
|
12
|
+
});
|
|
13
|
+
const edges = opts.edges.map((e) => ({
|
|
14
|
+
id: `e-${e.source}-${e.target}`,
|
|
15
|
+
source: e.source,
|
|
16
|
+
target: e.target,
|
|
17
|
+
}));
|
|
18
|
+
const plan = { version, taskId: opts.taskId, nodes, edges };
|
|
19
|
+
if (opts.meta !== undefined)
|
|
20
|
+
plan.meta = opts.meta;
|
|
21
|
+
return plan;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Convert tmd-plan generated tasks into an execution plan.
|
|
25
|
+
*
|
|
26
|
+
* Note:
|
|
27
|
+
* - The Rust generator may emit `dependencies` as 0-based task indexes.
|
|
28
|
+
* - In the persisted execution plan artifact, task dependencies are represented
|
|
29
|
+
* ONLY by `edges` (source -> target).
|
|
30
|
+
*/
|
|
31
|
+
export function executionPlanFromGeneratedTasks(opts) {
|
|
32
|
+
const agentId = opts.defaultAgentId ?? 'executor';
|
|
33
|
+
const nodes = opts.tasks.map((t, i) => ({
|
|
34
|
+
id: `task-${String(i + 1)}`,
|
|
35
|
+
title: t.title,
|
|
36
|
+
agentId,
|
|
37
|
+
}));
|
|
38
|
+
const edges = [];
|
|
39
|
+
for (let i = 0; i < opts.tasks.length; i++) {
|
|
40
|
+
const deps = opts.tasks[i]?.dependencies ?? [];
|
|
41
|
+
for (const d of deps) {
|
|
42
|
+
// Rust generator uses 0-based indices
|
|
43
|
+
const source = `task-${String(d + 1)}`;
|
|
44
|
+
const target = `task-${String(i + 1)}`;
|
|
45
|
+
edges.push({ source, target });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return buildExecutionPlan({
|
|
49
|
+
taskId: opts.taskId,
|
|
50
|
+
nodes,
|
|
51
|
+
edges,
|
|
52
|
+
meta: {
|
|
53
|
+
createdAt: new Date().toISOString(),
|
|
54
|
+
generatedBy: opts.generatedBy ?? 'tmd plan',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function validateExecutionPlan(plan) {
|
|
59
|
+
const issues = [];
|
|
60
|
+
const nodeIds = new Set();
|
|
61
|
+
for (const n of plan.nodes) {
|
|
62
|
+
if (nodeIds.has(n.id)) {
|
|
63
|
+
issues.push({ level: 'error', message: `Duplicate node id: ${n.id}` });
|
|
64
|
+
}
|
|
65
|
+
nodeIds.add(n.id);
|
|
66
|
+
}
|
|
67
|
+
for (const e of plan.edges) {
|
|
68
|
+
if (!nodeIds.has(e.source)) {
|
|
69
|
+
issues.push({ level: 'error', message: `Edge references missing source node: ${e.source}` });
|
|
70
|
+
}
|
|
71
|
+
if (!nodeIds.has(e.target)) {
|
|
72
|
+
issues.push({ level: 'error', message: `Edge references missing target node: ${e.target}` });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Cycle detection (Kahn)
|
|
76
|
+
const indeg = new Map();
|
|
77
|
+
const adj = new Map();
|
|
78
|
+
for (const id of nodeIds) {
|
|
79
|
+
indeg.set(id, 0);
|
|
80
|
+
adj.set(id, []);
|
|
81
|
+
}
|
|
82
|
+
for (const e of plan.edges) {
|
|
83
|
+
if (!nodeIds.has(e.source) || !nodeIds.has(e.target))
|
|
84
|
+
continue;
|
|
85
|
+
adj.get(e.source).push(e.target);
|
|
86
|
+
indeg.set(e.target, (indeg.get(e.target) ?? 0) + 1);
|
|
87
|
+
}
|
|
88
|
+
const q = [];
|
|
89
|
+
for (const [id, d] of indeg.entries()) {
|
|
90
|
+
if (d === 0)
|
|
91
|
+
q.push(id);
|
|
92
|
+
}
|
|
93
|
+
let visited = 0;
|
|
94
|
+
while (q.length) {
|
|
95
|
+
const id = q.shift();
|
|
96
|
+
visited += 1;
|
|
97
|
+
for (const nxt of adj.get(id) ?? []) {
|
|
98
|
+
const nd = (indeg.get(nxt) ?? 0) - 1;
|
|
99
|
+
indeg.set(nxt, nd);
|
|
100
|
+
if (nd === 0)
|
|
101
|
+
q.push(nxt);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (visited !== nodeIds.size) {
|
|
105
|
+
issues.push({ level: 'error', message: 'Dependency graph contains a cycle (not a DAG)' });
|
|
106
|
+
}
|
|
107
|
+
return issues;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=execution-plan.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface LLMPlanOptions {
|
|
2
|
+
description: string;
|
|
3
|
+
projectRoot?: string | undefined;
|
|
4
|
+
agentId?: string | undefined;
|
|
5
|
+
onStage?: ((stage: string) => void) | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface LLMPlanResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
changeId?: string | undefined;
|
|
10
|
+
error?: string | undefined;
|
|
11
|
+
tasksCount?: number | undefined;
|
|
12
|
+
specsCount?: number | undefined;
|
|
13
|
+
hasDesign?: boolean | undefined;
|
|
14
|
+
tasks?: Array<{
|
|
15
|
+
title: string;
|
|
16
|
+
deliverable: string;
|
|
17
|
+
validation: string;
|
|
18
|
+
dependencies: number[];
|
|
19
|
+
parallelizable: boolean;
|
|
20
|
+
complexity: string;
|
|
21
|
+
}> | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if spec features are enabled for this project
|
|
25
|
+
*/
|
|
26
|
+
export declare function isOpenSpecProject(root?: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Call the Rust binary to generate a proposal using LLM
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateLLMPlan(options: LLMPlanOptions): Promise<LLMPlanResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Display progress indicator while generating
|
|
33
|
+
*/
|
|
34
|
+
export declare function showGenerationProgress(): {
|
|
35
|
+
setStage(next: string): void;
|
|
36
|
+
stop(): void;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=llm-plan.d.ts.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
/**
|
|
6
|
+
* Check if spec features are enabled for this project
|
|
7
|
+
*/
|
|
8
|
+
export function isOpenSpecProject(root = '.') {
|
|
9
|
+
// Spec-enabled mode: prefer `.tmd/specs/` (new layout). For compatibility, allow legacy `openspec/specs/`.
|
|
10
|
+
const tmdSpecsDir = join(root, '.tmd', 'specs');
|
|
11
|
+
if (existsSync(tmdSpecsDir))
|
|
12
|
+
return true;
|
|
13
|
+
const legacySpecsDir = join(root, 'openspec', 'specs');
|
|
14
|
+
return existsSync(legacySpecsDir);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Call the Rust binary to generate a proposal using LLM
|
|
18
|
+
*/
|
|
19
|
+
export async function generateLLMPlan(options) {
|
|
20
|
+
const { description, projectRoot = '.', agentId = 'planner', onStage, } = options;
|
|
21
|
+
// Find the Rust binary (try release first, then debug)
|
|
22
|
+
const binaryName = 'tmd-plan-gen';
|
|
23
|
+
const releasePath = join(process.cwd(), 'target', 'release', binaryName);
|
|
24
|
+
const debugPath = join(process.cwd(), 'target', 'debug', binaryName);
|
|
25
|
+
const distPath = join(process.cwd(), 'dist', binaryName);
|
|
26
|
+
let binaryPath;
|
|
27
|
+
if (existsSync(distPath)) {
|
|
28
|
+
binaryPath = distPath;
|
|
29
|
+
}
|
|
30
|
+
else if (existsSync(releasePath)) {
|
|
31
|
+
binaryPath = releasePath;
|
|
32
|
+
}
|
|
33
|
+
else if (existsSync(debugPath)) {
|
|
34
|
+
binaryPath = debugPath;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: `Binary not found. Run 'cargo build -p tmd-plan --bin tmd-plan-gen' first.`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const args = [
|
|
44
|
+
description,
|
|
45
|
+
'--project-root', projectRoot,
|
|
46
|
+
'--agent-id', agentId,
|
|
47
|
+
];
|
|
48
|
+
const child = spawn(binaryPath, args);
|
|
49
|
+
let stdout = '';
|
|
50
|
+
let stderr = '';
|
|
51
|
+
let jsonOutput = '';
|
|
52
|
+
let inJson = false;
|
|
53
|
+
child.stdout.on('data', (data) => {
|
|
54
|
+
const text = data.toString();
|
|
55
|
+
stdout += text;
|
|
56
|
+
// Stream stage updates (best-effort)
|
|
57
|
+
for (const line of text.split('\n')) {
|
|
58
|
+
if (!line.startsWith('__STAGE__ '))
|
|
59
|
+
continue;
|
|
60
|
+
const stage = line.replace('__STAGE__ ', '').trim();
|
|
61
|
+
try {
|
|
62
|
+
onStage?.(stage);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// ignore callback errors
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Extract JSON output
|
|
69
|
+
const lines = text.split('\n');
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
if (line.includes('__JSON_START__')) {
|
|
72
|
+
inJson = true;
|
|
73
|
+
jsonOutput = '';
|
|
74
|
+
}
|
|
75
|
+
else if (line.includes('__JSON_END__')) {
|
|
76
|
+
inJson = false;
|
|
77
|
+
}
|
|
78
|
+
else if (inJson) {
|
|
79
|
+
jsonOutput += line;
|
|
80
|
+
}
|
|
81
|
+
else if (line.startsWith('__STAGE__ ')) {
|
|
82
|
+
// handled above; don't print
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Print non-JSON output
|
|
86
|
+
process.stdout.write(line + '\n');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
child.stderr.on('data', (data) => {
|
|
91
|
+
stderr += data.toString();
|
|
92
|
+
process.stderr.write(data);
|
|
93
|
+
});
|
|
94
|
+
child.on('close', (code) => {
|
|
95
|
+
if (code === 0) {
|
|
96
|
+
// Parse JSON output if available
|
|
97
|
+
if (jsonOutput) {
|
|
98
|
+
try {
|
|
99
|
+
const result = JSON.parse(jsonOutput);
|
|
100
|
+
resolve({
|
|
101
|
+
success: true,
|
|
102
|
+
changeId: result.change_id,
|
|
103
|
+
tasksCount: result.tasks?.length || 0,
|
|
104
|
+
specsCount: result.spec_deltas?.length || 0,
|
|
105
|
+
hasDesign: !!result.design,
|
|
106
|
+
tasks: Array.isArray(result.tasks) ? result.tasks : undefined,
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
// JSON parse failed, but command succeeded
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Fallback: extract change ID from stdout
|
|
115
|
+
const changeIdMatch = stdout.match(/Change ID: ([\w-]+)/);
|
|
116
|
+
resolve({
|
|
117
|
+
success: true,
|
|
118
|
+
changeId: changeIdMatch ? changeIdMatch[1] : undefined,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
resolve({
|
|
123
|
+
success: false,
|
|
124
|
+
error: stderr || `Command failed with exit code ${code}`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
child.on('error', (error) => {
|
|
129
|
+
resolve({
|
|
130
|
+
success: false,
|
|
131
|
+
error: `Failed to spawn binary: ${error.message}`,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Display progress indicator while generating
|
|
138
|
+
*/
|
|
139
|
+
export function showGenerationProgress() {
|
|
140
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
141
|
+
let i = 0;
|
|
142
|
+
let stage = 'Starting...';
|
|
143
|
+
const start = Date.now();
|
|
144
|
+
const interval = setInterval(() => {
|
|
145
|
+
const elapsedMs = Date.now() - start;
|
|
146
|
+
process.stdout.write(`\r${chalk.blue(frames[i])} ${stage} ${chalk.dim(`(${(elapsedMs / 1000).toFixed(1)}s)`)}`);
|
|
147
|
+
i = (i + 1) % frames.length;
|
|
148
|
+
}, 80);
|
|
149
|
+
return {
|
|
150
|
+
setStage(next) {
|
|
151
|
+
stage = next;
|
|
152
|
+
},
|
|
153
|
+
stop() {
|
|
154
|
+
clearInterval(interval);
|
|
155
|
+
process.stdout.write('\r');
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=llm-plan.js.map
|
package/dist/utils/openspec.js
CHANGED
|
@@ -50,7 +50,7 @@ export function createOpenSpecChange(description, taskId) {
|
|
|
50
50
|
`;
|
|
51
51
|
writeFileSync(join(changeDir, 'tasks.md'), tasksContent);
|
|
52
52
|
// Link TMD task to OpenSpec change
|
|
53
|
-
const metadataPath = join('tmd', 'plan', taskId, 'metadata.json');
|
|
53
|
+
const metadataPath = join('.tmd', 'plan', taskId, 'metadata.json');
|
|
54
54
|
if (existsSync(metadataPath)) {
|
|
55
55
|
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
56
56
|
metadata['openspecChange'] = changeId;
|
|
@@ -70,7 +70,7 @@ export function linkOpenSpecChange(taskId, changeId) {
|
|
|
70
70
|
return false;
|
|
71
71
|
}
|
|
72
72
|
// Link in task metadata
|
|
73
|
-
const metadataPath = join('tmd', 'plan', taskId, 'metadata.json');
|
|
73
|
+
const metadataPath = join('.tmd', 'plan', taskId, 'metadata.json');
|
|
74
74
|
if (existsSync(metadataPath)) {
|
|
75
75
|
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
76
76
|
metadata['openspecChange'] = changeId;
|
package/dist/utils/paths.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export declare function getTmdDir(): string;
|
|
2
|
+
export declare function getAgentsDir(): string;
|
|
3
|
+
/**
|
|
4
|
+
* Ensures .tmd/ and .agents/ exist under projectRoot. Idempotent; does not overwrite.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ensureProjectDirs(projectRoot: string): void;
|
|
2
7
|
export declare function getTaskDir(phase: string, taskId: string): string;
|
|
3
8
|
export declare function ensureDir(path: string): void;
|
|
4
9
|
export declare function getPlanDir(taskId: string): string;
|
package/dist/utils/paths.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { existsSync, mkdirSync } from 'fs';
|
|
3
3
|
const TMD_DIR = '.tmd';
|
|
4
|
+
const AGENTS_DIR = '.agents';
|
|
4
5
|
export function getTmdDir() {
|
|
5
6
|
return TMD_DIR;
|
|
6
7
|
}
|
|
8
|
+
export function getAgentsDir() {
|
|
9
|
+
return AGENTS_DIR;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Ensures .tmd/ and .agents/ exist under projectRoot. Idempotent; does not overwrite.
|
|
13
|
+
*/
|
|
14
|
+
export function ensureProjectDirs(projectRoot) {
|
|
15
|
+
ensureDir(join(projectRoot, TMD_DIR));
|
|
16
|
+
ensureDir(join(projectRoot, AGENTS_DIR));
|
|
17
|
+
}
|
|
7
18
|
export function getTaskDir(phase, taskId) {
|
|
8
19
|
return join(TMD_DIR, phase, taskId);
|
|
9
20
|
}
|
package/dist/utils/skills.js
CHANGED
|
@@ -125,8 +125,7 @@ async function executeStepsConcurrently(steps, maxConcurrency, taskId, workingDi
|
|
|
125
125
|
if (executing.length >= maxConcurrency) {
|
|
126
126
|
const completed = await Promise.race(executing);
|
|
127
127
|
results.push(completed);
|
|
128
|
-
|
|
129
|
-
executing.splice(executing.indexOf(promise), 1);
|
|
128
|
+
void executing.splice(executing.indexOf(promise), 1);
|
|
130
129
|
}
|
|
131
130
|
}
|
|
132
131
|
// Wait for remaining steps
|
|
@@ -2,5 +2,6 @@ import type { TaskMetadata } from '../types.js';
|
|
|
2
2
|
export type TaskStatus = 'planning' | 'doing' | 'checking' | 'acting' | 'completed';
|
|
3
3
|
export declare function getTaskStatus(taskId: string): TaskStatus;
|
|
4
4
|
export declare function updateTaskStatus(taskId: string, status: TaskStatus): void;
|
|
5
|
+
export declare function updateTaskMetadata(taskId: string, patch: Partial<TaskMetadata>): void;
|
|
5
6
|
export declare function getTaskMetadata(taskId: string): TaskMetadata;
|
|
6
7
|
//# sourceMappingURL=task-status.d.ts.map
|
|
@@ -46,6 +46,17 @@ export function updateTaskStatus(taskId, status) {
|
|
|
46
46
|
}
|
|
47
47
|
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
48
48
|
}
|
|
49
|
+
export function updateTaskMetadata(taskId, patch) {
|
|
50
|
+
const planDir = getPlanDir(taskId);
|
|
51
|
+
const metadataPath = join(planDir, 'metadata.json');
|
|
52
|
+
ensureDir(planDir);
|
|
53
|
+
const metadata = existsSync(metadataPath)
|
|
54
|
+
? JSON.parse(readFileSync(metadataPath, 'utf-8'))
|
|
55
|
+
: {};
|
|
56
|
+
Object.assign(metadata, patch);
|
|
57
|
+
metadata.lastUpdated = new Date().toISOString();
|
|
58
|
+
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
59
|
+
}
|
|
49
60
|
export function getTaskMetadata(taskId) {
|
|
50
61
|
const metadataPath = join(getPlanDir(taskId), 'metadata.json');
|
|
51
62
|
if (existsSync(metadataPath)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmddev/tmd",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Task Markdown Driven - A lightweight PDCA cycle management framework integrated with OpenSpec",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -55,10 +55,9 @@
|
|
|
55
55
|
"@types/js-yaml": "^4.0.9",
|
|
56
56
|
"@types/node": "^22.12.0",
|
|
57
57
|
"@vitest/ui": "^3.2.4",
|
|
58
|
-
"
|
|
58
|
+
"oxlint": "^1.41.0",
|
|
59
59
|
"tsx": "^4.19.2",
|
|
60
60
|
"typescript": "^5.7.3",
|
|
61
|
-
"typescript-eslint": "^8.50.1",
|
|
62
61
|
"vitest": "^3.2.4"
|
|
63
62
|
},
|
|
64
63
|
"engines": {
|
|
@@ -66,7 +65,7 @@
|
|
|
66
65
|
"bun": ">=1.0"
|
|
67
66
|
},
|
|
68
67
|
"scripts": {
|
|
69
|
-
"lint": "
|
|
68
|
+
"lint": "oxlint src/ build.ts",
|
|
70
69
|
"build": "tsx build.ts",
|
|
71
70
|
"build:native": "node scripts/build-native.mjs",
|
|
72
71
|
"dev": "tsc --watch",
|
package/dist/utils/skillssh.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
interface SkillsshResult {
|
|
2
|
-
success: boolean;
|
|
3
|
-
skillName?: string;
|
|
4
|
-
skillDir?: string;
|
|
5
|
-
error?: string;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Fetches and converts a skill from skills.sh to TMD format
|
|
9
|
-
*/
|
|
10
|
-
export declare function addSkillFromSkillssh(ownerRepo: string, skillName?: string): Promise<SkillsshResult>;
|
|
11
|
-
export {};
|
|
12
|
-
//# sourceMappingURL=skillssh.d.ts.map
|