@pedrocivita/tocket 1.2.0 → 2.1.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/README.md +38 -7
- package/dist/commands/config.cmd.d.ts +2 -0
- package/dist/commands/config.cmd.js +102 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +68 -0
- package/dist/commands/eject.cmd.d.ts +6 -0
- package/dist/commands/eject.cmd.js +65 -0
- package/dist/commands/focus.cmd.d.ts +7 -0
- package/dist/commands/focus.cmd.js +52 -0
- package/dist/commands/generate.cmd.js +81 -29
- package/dist/commands/init.cmd.js +31 -10
- package/dist/commands/status.cmd.d.ts +2 -0
- package/dist/commands/status.cmd.js +85 -0
- package/dist/commands/sync.cmd.js +9 -13
- package/dist/commands/validate.cmd.js +7 -6
- package/dist/index.js +21 -2
- package/dist/tests/config.test.d.ts +1 -0
- package/dist/tests/config.test.js +68 -0
- package/dist/tests/eject.test.d.ts +1 -0
- package/dist/tests/eject.test.js +88 -0
- package/dist/tests/focus.test.d.ts +1 -0
- package/dist/tests/focus.test.js +130 -0
- package/dist/tests/git.test.d.ts +1 -0
- package/dist/tests/git.test.js +61 -0
- package/dist/tests/status.test.d.ts +1 -0
- package/dist/tests/status.test.js +34 -0
- package/dist/tests/theme.test.d.ts +1 -0
- package/dist/tests/theme.test.js +58 -0
- package/dist/utils/config.d.ts +16 -0
- package/dist/utils/config.js +33 -0
- package/dist/utils/git.d.ts +6 -0
- package/dist/utils/git.js +79 -0
- package/dist/utils/theme.d.ts +13 -0
- package/dist/utils/theme.js +38 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -32,24 +32,55 @@ The CLI just automates the scaffolding.
|
|
|
32
32
|
## Quick Start
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
+
# Interactive dashboard — guided entry point
|
|
36
|
+
npx @pedrocivita/tocket
|
|
37
|
+
|
|
35
38
|
# Scaffold a new workspace (creates .context/, TOCKET.md, CLAUDE.md, GEMINI.md)
|
|
36
39
|
npx @pedrocivita/tocket init
|
|
37
40
|
|
|
38
|
-
# Generate a payload XML
|
|
41
|
+
# Generate a payload XML with smart git integration
|
|
39
42
|
npx @pedrocivita/tocket generate
|
|
40
43
|
|
|
41
44
|
# Sync session progress into Memory Bank
|
|
42
45
|
npx @pedrocivita/tocket sync
|
|
46
|
+
|
|
47
|
+
# Update the current focus
|
|
48
|
+
npx @pedrocivita/tocket focus "Refactoring payment module"
|
|
49
|
+
|
|
50
|
+
# Remove all Tocket files (with confirmation)
|
|
51
|
+
npx @pedrocivita/tocket eject
|
|
43
52
|
```
|
|
44
53
|
|
|
45
54
|
## Commands
|
|
46
55
|
|
|
47
|
-
| Command | What it does
|
|
48
|
-
| ----------------- |
|
|
49
|
-
| `tocket
|
|
50
|
-
| `tocket
|
|
51
|
-
| `tocket
|
|
52
|
-
| `tocket
|
|
56
|
+
| Command | What it does |
|
|
57
|
+
| ----------------- | -------------------------------------------------------------------- |
|
|
58
|
+
| `tocket` | Interactive dashboard with guided menu |
|
|
59
|
+
| `tocket init` | Scaffolds `.context/`, `TOCKET.md`, `CLAUDE.md`, `GEMINI.md` |
|
|
60
|
+
| `tocket generate` | Smart payload builder — auto-fills scope from git, multi-task support |
|
|
61
|
+
| `tocket sync` | Appends session summary + git log to `.context/progress.md` |
|
|
62
|
+
| `tocket validate` | Checks if the current directory has a valid Tocket Memory Bank |
|
|
63
|
+
| `tocket focus` | Update the Current Focus in `.context/activeContext.md` |
|
|
64
|
+
| `tocket status` | Quick overview of workspace, focus, branch, and agents |
|
|
65
|
+
| `tocket config` | Manage global settings (`~/.tocketrc.json`) |
|
|
66
|
+
| `tocket eject` | Remove all Tocket files from the workspace |
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
Set global defaults so you don't repeat yourself:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Interactive setup
|
|
74
|
+
tocket config
|
|
75
|
+
|
|
76
|
+
# Or use flags (CI-friendly)
|
|
77
|
+
tocket config --author "Your Name" --priority medium --skills "core,lsp"
|
|
78
|
+
|
|
79
|
+
# View current config
|
|
80
|
+
tocket config --show
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Config is stored at `~/.tocketrc.json` and pre-fills author, priority, and skills in all commands.
|
|
53
84
|
|
|
54
85
|
## How Triangulation works
|
|
55
86
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { input, select, confirm } from "@inquirer/prompts";
|
|
2
|
+
import { getConfig, saveConfig, resetConfig, getConfigPath, } from "../utils/config.js";
|
|
3
|
+
import { success, heading, dim } from "../utils/theme.js";
|
|
4
|
+
export function registerConfigCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("config")
|
|
7
|
+
.description("Manage global Tocket configuration (~/.tocketrc.json)")
|
|
8
|
+
.option("--author <name>", "Set default author name")
|
|
9
|
+
.option("--agent <name>", "Set default agent name")
|
|
10
|
+
.option("--priority <level>", "Set default priority (high|medium|low)")
|
|
11
|
+
.option("--skills <list>", "Set default skills (comma-separated)")
|
|
12
|
+
.option("--show", "Display current configuration")
|
|
13
|
+
.option("--path", "Show config file path")
|
|
14
|
+
.option("--reset", "Reset configuration to defaults")
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
// --path: print and exit
|
|
17
|
+
if (options.path) {
|
|
18
|
+
console.log(getConfigPath());
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// --show: display current config
|
|
22
|
+
if (options.show) {
|
|
23
|
+
const config = await getConfig();
|
|
24
|
+
console.log(heading("\n Current Configuration\n"));
|
|
25
|
+
console.log(JSON.stringify(config, null, 2)
|
|
26
|
+
.split("\n")
|
|
27
|
+
.map((l) => " " + l)
|
|
28
|
+
.join("\n"));
|
|
29
|
+
console.log(dim(`\n Path: ${getConfigPath()}\n`));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// --reset: clear config
|
|
33
|
+
if (options.reset) {
|
|
34
|
+
const ok = await confirm({
|
|
35
|
+
message: "Reset all configuration to defaults?",
|
|
36
|
+
default: false,
|
|
37
|
+
});
|
|
38
|
+
if (ok) {
|
|
39
|
+
await resetConfig();
|
|
40
|
+
console.log(success("Configuration reset."));
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Non-interactive: handle individual flags
|
|
45
|
+
const hasFlags = options.author || options.agent || options.priority || options.skills;
|
|
46
|
+
if (hasFlags) {
|
|
47
|
+
const config = await getConfig();
|
|
48
|
+
if (options.author)
|
|
49
|
+
config.author = options.author;
|
|
50
|
+
if (options.agent)
|
|
51
|
+
config.defaultAgent = options.agent;
|
|
52
|
+
if (options.priority) {
|
|
53
|
+
if (!config.defaults)
|
|
54
|
+
config.defaults = {};
|
|
55
|
+
config.defaults.priority = options.priority;
|
|
56
|
+
}
|
|
57
|
+
if (options.skills) {
|
|
58
|
+
if (!config.defaults)
|
|
59
|
+
config.defaults = {};
|
|
60
|
+
config.defaults.skills = options.skills;
|
|
61
|
+
}
|
|
62
|
+
await saveConfig(config);
|
|
63
|
+
console.log(success("Configuration updated."));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Interactive TUI mode
|
|
67
|
+
const current = await getConfig();
|
|
68
|
+
console.log(heading("\n Tocket Configuration\n"));
|
|
69
|
+
const author = await input({
|
|
70
|
+
message: "Author name:",
|
|
71
|
+
default: current.author || undefined,
|
|
72
|
+
});
|
|
73
|
+
const agent = await input({
|
|
74
|
+
message: "Default agent name:",
|
|
75
|
+
default: current.defaultAgent || undefined,
|
|
76
|
+
});
|
|
77
|
+
const priority = await select({
|
|
78
|
+
message: "Default priority:",
|
|
79
|
+
choices: [
|
|
80
|
+
{ value: "high", name: "high" },
|
|
81
|
+
{ value: "medium", name: "medium" },
|
|
82
|
+
{ value: "low", name: "low" },
|
|
83
|
+
],
|
|
84
|
+
default: current.defaults?.priority ?? "medium",
|
|
85
|
+
});
|
|
86
|
+
const skills = await input({
|
|
87
|
+
message: "Default skills (comma-separated):",
|
|
88
|
+
default: current.defaults?.skills || undefined,
|
|
89
|
+
});
|
|
90
|
+
await saveConfig({
|
|
91
|
+
...current,
|
|
92
|
+
author: author || undefined,
|
|
93
|
+
defaultAgent: agent || undefined,
|
|
94
|
+
defaults: {
|
|
95
|
+
priority: priority,
|
|
96
|
+
skills: skills || undefined,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
console.log("\n" + success("Configuration saved."));
|
|
100
|
+
console.log(dim(` Path: ${getConfigPath()}\n`));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { banner, heading, success, dim, warn } from "../utils/theme.js";
|
|
6
|
+
import { getConfig } from "../utils/config.js";
|
|
7
|
+
function extractFocus(content) {
|
|
8
|
+
const match = content.match(/## Current Focus\s*\n+(.+)/);
|
|
9
|
+
if (!match?.[1])
|
|
10
|
+
return "";
|
|
11
|
+
const line = match[1].trim();
|
|
12
|
+
if (line.startsWith("_") || line.includes("No active tasks"))
|
|
13
|
+
return "";
|
|
14
|
+
return line.length > 80 ? line.substring(0, 77) + "..." : line;
|
|
15
|
+
}
|
|
16
|
+
export async function showDashboard(program) {
|
|
17
|
+
const config = await getConfig();
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
const hasWorkspace = existsSync(join(cwd, ".context"));
|
|
20
|
+
if (!config.theme?.disableBanner) {
|
|
21
|
+
console.log(banner());
|
|
22
|
+
}
|
|
23
|
+
console.log(heading(" Dashboard\n"));
|
|
24
|
+
if (hasWorkspace) {
|
|
25
|
+
console.log(" " + success("Workspace detected"));
|
|
26
|
+
try {
|
|
27
|
+
const ctx = await readFile(join(cwd, ".context", "activeContext.md"), "utf-8");
|
|
28
|
+
const focus = extractFocus(ctx);
|
|
29
|
+
if (focus) {
|
|
30
|
+
console.log(" " + dim(`Focus: ${focus}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// activeContext.md missing or unreadable — ignore
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
console.log(" " + warn("No workspace in this directory"));
|
|
39
|
+
console.log(" " + dim("Run init to scaffold a Tocket workspace here.\n"));
|
|
40
|
+
}
|
|
41
|
+
console.log();
|
|
42
|
+
const choices = hasWorkspace
|
|
43
|
+
? [
|
|
44
|
+
{ value: "generate", name: "Generate payload" },
|
|
45
|
+
{ value: "sync", name: "Sync progress" },
|
|
46
|
+
{ value: "validate", name: "Validate workspace" },
|
|
47
|
+
{ value: "focus", name: "Update focus" },
|
|
48
|
+
{ value: "status", name: "Workspace status" },
|
|
49
|
+
{ value: "config", name: "Configure settings" },
|
|
50
|
+
{ value: "eject", name: "Eject workspace" },
|
|
51
|
+
{ value: "exit", name: "Exit" },
|
|
52
|
+
]
|
|
53
|
+
: [
|
|
54
|
+
{ value: "init", name: "Initialize workspace" },
|
|
55
|
+
{ value: "config", name: "Configure settings" },
|
|
56
|
+
{ value: "exit", name: "Exit" },
|
|
57
|
+
];
|
|
58
|
+
const action = await select({
|
|
59
|
+
message: "What would you like to do?",
|
|
60
|
+
choices,
|
|
61
|
+
});
|
|
62
|
+
if (action === "exit") {
|
|
63
|
+
console.log(dim("\n Goodbye!\n"));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log();
|
|
67
|
+
await program.parseAsync(["node", "tocket", action]);
|
|
68
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/** Files created by `tocket init` that eject should remove. */
|
|
3
|
+
export declare const EJECT_FILES: readonly ["TOCKET.md", "CLAUDE.md", "GEMINI.md", ".cursorrules"];
|
|
4
|
+
/** Directories created by `tocket init` that eject should remove. */
|
|
5
|
+
export declare const EJECT_DIRS: readonly [".context"];
|
|
6
|
+
export declare function registerEjectCommand(program: Command): void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { rm } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { success, warn, info, dim } from "../utils/theme.js";
|
|
6
|
+
/** Files created by `tocket init` that eject should remove. */
|
|
7
|
+
export const EJECT_FILES = [
|
|
8
|
+
"TOCKET.md",
|
|
9
|
+
"CLAUDE.md",
|
|
10
|
+
"GEMINI.md",
|
|
11
|
+
".cursorrules",
|
|
12
|
+
];
|
|
13
|
+
/** Directories created by `tocket init` that eject should remove. */
|
|
14
|
+
export const EJECT_DIRS = [".context"];
|
|
15
|
+
export function registerEjectCommand(program) {
|
|
16
|
+
program
|
|
17
|
+
.command("eject")
|
|
18
|
+
.description("Remove all Tocket files from the current workspace")
|
|
19
|
+
.option("-f, --force", "Skip confirmation prompt")
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const contextDir = join(cwd, ".context");
|
|
23
|
+
if (!existsSync(contextDir)) {
|
|
24
|
+
console.log(warn("No Tocket workspace found in this directory."));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!options.force) {
|
|
28
|
+
const ok = await confirm({
|
|
29
|
+
message: "This will permanently remove .context/, CLAUDE.md, GEMINI.md, TOCKET.md, and .cursorrules. Continue?",
|
|
30
|
+
default: false,
|
|
31
|
+
});
|
|
32
|
+
if (!ok) {
|
|
33
|
+
console.log(dim("\n Cancelled.\n"));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
let removedCount = 0;
|
|
38
|
+
for (const dir of EJECT_DIRS) {
|
|
39
|
+
const fullPath = join(cwd, dir);
|
|
40
|
+
if (existsSync(fullPath)) {
|
|
41
|
+
await rm(fullPath, { recursive: true, force: true });
|
|
42
|
+
console.log(info(`Removed ${dir}/`));
|
|
43
|
+
removedCount++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const file of EJECT_FILES) {
|
|
47
|
+
const fullPath = join(cwd, file);
|
|
48
|
+
if (existsSync(fullPath)) {
|
|
49
|
+
await rm(fullPath, { force: true });
|
|
50
|
+
console.log(info(`Removed ${file}`));
|
|
51
|
+
removedCount++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (removedCount === 0) {
|
|
55
|
+
console.log(warn("Nothing to remove."));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log("\n" +
|
|
59
|
+
success("Tocket workspace ejected.") +
|
|
60
|
+
" " +
|
|
61
|
+
dim("Global config (~/.tocketrc.json) was not touched.") +
|
|
62
|
+
"\n");
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the content under ## Current Focus with the new message.
|
|
4
|
+
* Exported for testing.
|
|
5
|
+
*/
|
|
6
|
+
export declare function replaceFocusSection(content: string, newFocus: string): string;
|
|
7
|
+
export declare function registerFocusCommand(program: Command): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { input } from "@inquirer/prompts";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { success, error as themeError, info } from "../utils/theme.js";
|
|
6
|
+
/**
|
|
7
|
+
* Replaces the content under ## Current Focus with the new message.
|
|
8
|
+
* Exported for testing.
|
|
9
|
+
*/
|
|
10
|
+
export function replaceFocusSection(content, newFocus) {
|
|
11
|
+
const regex = /(## Current Focus[ \t]*\n)[\s\S]*?(?=\n## |\n*$)/;
|
|
12
|
+
if (!regex.test(content)) {
|
|
13
|
+
return content.trimEnd() + "\n\n## Current Focus\n\n" + newFocus + "\n";
|
|
14
|
+
}
|
|
15
|
+
return content.replace(regex, `$1\n${newFocus}\n`);
|
|
16
|
+
}
|
|
17
|
+
export function registerFocusCommand(program) {
|
|
18
|
+
program
|
|
19
|
+
.command("focus")
|
|
20
|
+
.description("Update the Current Focus in activeContext.md")
|
|
21
|
+
.argument("[message...]", "Focus message (prompted if omitted)")
|
|
22
|
+
.action(async (messageParts) => {
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
const contextDir = join(cwd, ".context");
|
|
25
|
+
if (!existsSync(contextDir)) {
|
|
26
|
+
console.error(themeError("No .context/ directory found. Run 'tocket init' first."));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const filePath = join(contextDir, "activeContext.md");
|
|
31
|
+
if (!existsSync(filePath)) {
|
|
32
|
+
console.error(themeError("activeContext.md not found. Run 'tocket init' first."));
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let message = messageParts.join(" ").trim();
|
|
37
|
+
if (!message) {
|
|
38
|
+
message = await input({ message: "What is the current focus?" });
|
|
39
|
+
message = message.trim();
|
|
40
|
+
}
|
|
41
|
+
if (!message) {
|
|
42
|
+
console.error(themeError("Focus message cannot be empty."));
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const content = await readFile(filePath, "utf-8");
|
|
47
|
+
const updated = replaceFocusSection(content, message);
|
|
48
|
+
await writeFile(filePath, updated, "utf-8");
|
|
49
|
+
console.log(info("Focus updated in .context/activeContext.md"));
|
|
50
|
+
console.log(" " + success(message));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import { input, select } from "@inquirer/prompts";
|
|
1
|
+
import { input, select, confirm } from "@inquirer/prompts";
|
|
2
2
|
import clipboard from "clipboardy";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { success, heading, dim, banner } from "../utils/theme.js";
|
|
4
|
+
import { getConfig } from "../utils/config.js";
|
|
5
|
+
import { getStagedFiles, getModifiedFiles } from "../utils/git.js";
|
|
6
|
+
function buildPayloadXml(tasks) {
|
|
7
|
+
const first = tasks[0];
|
|
8
|
+
const skillsAttr = first.skills.trim()
|
|
9
|
+
? `\n <skills>${first.skills.trim()}</skills>`
|
|
6
10
|
: "";
|
|
11
|
+
const taskBlocks = tasks
|
|
12
|
+
.map((t, i) => ` <task id="${i + 1}" type="create | edit | delete">
|
|
13
|
+
<target><!-- file/path --></target>
|
|
14
|
+
<action>${t.intent}</action>
|
|
15
|
+
<spec><!-- Detailed specification --></spec>
|
|
16
|
+
<done><!-- Definition of done --></done>
|
|
17
|
+
</task>`)
|
|
18
|
+
.join("\n");
|
|
7
19
|
return `<payload version="2.0">
|
|
8
20
|
<meta>
|
|
9
|
-
<intent>${
|
|
10
|
-
<scope>${
|
|
11
|
-
<priority>${
|
|
21
|
+
<intent>${first.intent}</intent>
|
|
22
|
+
<scope>${first.scope}</scope>${skillsAttr}
|
|
23
|
+
<priority>${first.priority}</priority>
|
|
12
24
|
</meta>
|
|
13
25
|
|
|
14
26
|
<context>
|
|
@@ -16,12 +28,7 @@ function buildPayloadXml(opts) {
|
|
|
16
28
|
</context>
|
|
17
29
|
|
|
18
30
|
<tasks>
|
|
19
|
-
|
|
20
|
-
<target><!-- file/path --></target>
|
|
21
|
-
<action><!-- What to do --></action>
|
|
22
|
-
<spec><!-- Detailed specification --></spec>
|
|
23
|
-
<done><!-- Definition of done --></done>
|
|
24
|
-
</task>
|
|
31
|
+
${taskBlocks}
|
|
25
32
|
</tasks>
|
|
26
33
|
|
|
27
34
|
<validate>
|
|
@@ -29,26 +36,71 @@ function buildPayloadXml(opts) {
|
|
|
29
36
|
</validate>
|
|
30
37
|
</payload>`;
|
|
31
38
|
}
|
|
39
|
+
function suggestScope() {
|
|
40
|
+
const staged = getStagedFiles();
|
|
41
|
+
const modified = getModifiedFiles();
|
|
42
|
+
const all = [...new Set([...staged, ...modified])];
|
|
43
|
+
return all.join(", ");
|
|
44
|
+
}
|
|
32
45
|
export function registerGenerateCommand(program) {
|
|
33
46
|
program
|
|
34
47
|
.command("generate")
|
|
35
48
|
.description("Build payload XMLs interactively for Architect-Executor handoff")
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
.option("--no-preview", "Skip payload preview before copying")
|
|
50
|
+
.action(async (options) => {
|
|
51
|
+
const config = await getConfig();
|
|
52
|
+
if (!config.theme?.disableBanner) {
|
|
53
|
+
console.log(banner());
|
|
54
|
+
}
|
|
55
|
+
const tasks = [];
|
|
56
|
+
const suggestedScope = suggestScope();
|
|
57
|
+
const defaultPriority = config.defaults?.priority ?? "medium";
|
|
58
|
+
const defaultSkills = config.defaults?.skills ?? "";
|
|
59
|
+
let addMore = true;
|
|
60
|
+
while (addMore) {
|
|
61
|
+
const taskNum = tasks.length + 1;
|
|
62
|
+
if (taskNum > 1) {
|
|
63
|
+
console.log(heading(`\n Task ${taskNum}\n`));
|
|
64
|
+
}
|
|
65
|
+
const intent = await input({ message: "Intent (goal in one line):" });
|
|
66
|
+
const scope = await input({
|
|
67
|
+
message: "Scope (files/folders affected):",
|
|
68
|
+
default: taskNum === 1 && suggestedScope ? suggestedScope : undefined,
|
|
69
|
+
});
|
|
70
|
+
const priority = await select({
|
|
71
|
+
message: "Priority:",
|
|
72
|
+
choices: [
|
|
73
|
+
{ value: "high", name: "high" },
|
|
74
|
+
{ value: "medium", name: "medium" },
|
|
75
|
+
{ value: "low", name: "low" },
|
|
76
|
+
],
|
|
77
|
+
default: defaultPriority,
|
|
78
|
+
});
|
|
79
|
+
const skills = await input({
|
|
80
|
+
message: "Skills/plugins (comma-separated, optional):",
|
|
81
|
+
default: taskNum === 1 && defaultSkills ? defaultSkills : undefined,
|
|
82
|
+
});
|
|
83
|
+
tasks.push({ intent, scope, priority, skills });
|
|
84
|
+
addMore = await confirm({
|
|
85
|
+
message: "Add another task?",
|
|
86
|
+
default: false,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const xml = buildPayloadXml(tasks);
|
|
90
|
+
// Preview
|
|
91
|
+
if (options.preview !== false) {
|
|
92
|
+
console.log(dim("\n--- Payload Preview ---"));
|
|
93
|
+
const lines = xml.split("\n");
|
|
94
|
+
const preview = lines.length > 20
|
|
95
|
+
? lines.slice(0, 20).join("\n") + dim("\n ... (" + (lines.length - 20) + " more lines)")
|
|
96
|
+
: lines.join("\n");
|
|
97
|
+
console.log(dim(preview));
|
|
98
|
+
console.log(dim("--- End Preview ---\n"));
|
|
99
|
+
}
|
|
51
100
|
clipboard.writeSync(xml);
|
|
52
|
-
console.log(
|
|
101
|
+
console.log(success(`Payload XML (v2.0) copied to clipboard!`) +
|
|
102
|
+
dim(` ${tasks.length} task(s).`) +
|
|
103
|
+
"\n" +
|
|
104
|
+
dim(" Paste it into your Architect to continue.\n"));
|
|
53
105
|
});
|
|
54
106
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { input } from "@inquirer/prompts";
|
|
1
|
+
import { input, confirm } from "@inquirer/prompts";
|
|
2
2
|
import { mkdir, readFile, writeFile, access } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { banner, heading, info, success, dim } from "../utils/theme.js";
|
|
5
|
+
import { getConfig } from "../utils/config.js";
|
|
4
6
|
import { claudeMd, geminiMd, tocketMd, activeContextMd, systemPatternsMd, productContextMd, techContextMd, progressMd, cursorrulesMd, } from "../templates/memory-bank.js";
|
|
5
7
|
async function fileExists(path) {
|
|
6
8
|
try {
|
|
@@ -116,22 +118,28 @@ export function registerInitCommand(program) {
|
|
|
116
118
|
program
|
|
117
119
|
.command("init")
|
|
118
120
|
.description("Scaffold an agentic workspace with Memory Bank and triangulation config")
|
|
119
|
-
.
|
|
121
|
+
.option("-f, --force", "Overwrite existing files without prompting")
|
|
122
|
+
.action(async (options) => {
|
|
123
|
+
const force = options.force ?? false;
|
|
120
124
|
const cwd = process.cwd();
|
|
125
|
+
const globalConfig = await getConfig();
|
|
126
|
+
if (!globalConfig.theme?.disableBanner) {
|
|
127
|
+
console.log(banner());
|
|
128
|
+
}
|
|
121
129
|
const { stack, detectedName, detectedDescription } = await detectStack(cwd);
|
|
122
130
|
const hasDetection = Boolean(stack.language);
|
|
123
131
|
if (hasDetection) {
|
|
124
|
-
console.log("
|
|
132
|
+
console.log(heading(" Auto-detected stack:\n"));
|
|
125
133
|
if (stack.language)
|
|
126
|
-
console.log(`
|
|
134
|
+
console.log(info(`Language: ${stack.language}`));
|
|
127
135
|
if (stack.runtime)
|
|
128
|
-
console.log(`
|
|
136
|
+
console.log(info(`Runtime: ${stack.runtime}`));
|
|
129
137
|
if (stack.build)
|
|
130
|
-
console.log(`
|
|
138
|
+
console.log(info(`Build: ${stack.build}`));
|
|
131
139
|
if (stack.framework)
|
|
132
|
-
console.log(`
|
|
140
|
+
console.log(info(`Framework: ${stack.framework}`));
|
|
133
141
|
if (stack.extras.length)
|
|
134
|
-
console.log(`
|
|
142
|
+
console.log(info(`Extras: ${stack.extras.join(", ")}`));
|
|
135
143
|
console.log();
|
|
136
144
|
}
|
|
137
145
|
const projectName = await input({
|
|
@@ -163,9 +171,22 @@ export function registerInitCommand(program) {
|
|
|
163
171
|
];
|
|
164
172
|
for (const [filePath, content] of files) {
|
|
165
173
|
const fullPath = join(cwd, filePath);
|
|
174
|
+
const exists = await fileExists(fullPath);
|
|
175
|
+
if (exists && !force) {
|
|
176
|
+
const overwrite = await confirm({
|
|
177
|
+
message: `${filePath} already exists. Overwrite?`,
|
|
178
|
+
default: false,
|
|
179
|
+
});
|
|
180
|
+
if (!overwrite) {
|
|
181
|
+
console.log(" " + dim(`skipped ${filePath}`));
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
166
185
|
await writeFile(fullPath, content, "utf-8");
|
|
167
|
-
console.log(
|
|
186
|
+
console.log(" " + success(`${exists ? "updated" : "created"} ${filePath}`));
|
|
168
187
|
}
|
|
169
|
-
console.log(
|
|
188
|
+
console.log("\n" + success(`Workspace initialized for ${projectName}!`) +
|
|
189
|
+
(hasDetection ? dim(" Stack pre-populated from package.json.") : "") +
|
|
190
|
+
"\n" + dim(" Next: run tocket generate to create your first payload.\n"));
|
|
170
191
|
});
|
|
171
192
|
}
|