@pedrocivita/tocket 2.0.0 → 2.2.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 +193 -132
- package/dist/commands/dashboard.js +5 -0
- package/dist/commands/doctor.cmd.d.ts +9 -0
- package/dist/commands/doctor.cmd.js +191 -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.d.ts +8 -0
- package/dist/commands/generate.cmd.js +27 -10
- package/dist/commands/init.cmd.js +15 -3
- package/dist/commands/lint.cmd.d.ts +12 -0
- package/dist/commands/lint.cmd.js +269 -0
- package/dist/commands/status.cmd.d.ts +2 -0
- package/dist/commands/status.cmd.js +85 -0
- package/dist/commands/sync.cmd.js +3 -2
- package/dist/commands/validate.cmd.d.ts +7 -0
- package/dist/commands/validate.cmd.js +3 -3
- package/dist/index.js +10 -0
- package/dist/tests/doctor.test.d.ts +1 -0
- package/dist/tests/doctor.test.js +102 -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/generate.test.d.ts +1 -0
- package/dist/tests/generate.test.js +67 -0
- package/dist/tests/git.test.js +9 -1
- package/dist/tests/init.test.d.ts +1 -0
- package/dist/tests/init.test.js +69 -0
- package/dist/tests/lint.test.d.ts +1 -0
- package/dist/tests/lint.test.js +120 -0
- package/dist/tests/status.test.d.ts +1 -0
- package/dist/tests/status.test.js +34 -0
- package/dist/tests/validate.test.d.ts +1 -0
- package/dist/tests/validate.test.js +92 -0
- package/dist/utils/git.d.ts +1 -0
- package/dist/utils/git.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,132 +1,193 @@
|
|
|
1
|
-

|
|
2
|
-
[](https://www.npmjs.com/package/@pedrocivita/tocket)
|
|
3
|
-
[](LICENSE)
|
|
4
|
-
|
|
5
|
-
# Tocket
|
|
6
|
-
|
|
7
|
-
**The Context Engineering Framework for Multi-Agent Workspaces**
|
|
8
|
-
|
|
9
|
-
When multiple
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
|
|
65
|
-
tocket
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
#
|
|
71
|
-
tocket
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
1
|
+

|
|
2
|
+
[](https://www.npmjs.com/package/@pedrocivita/tocket)
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
|
|
5
|
+
# Tocket
|
|
6
|
+
|
|
7
|
+
**The Context Engineering Framework for Multi-Agent Workspaces**
|
|
8
|
+
|
|
9
|
+
AI agents forget everything between sessions. When multiple agents work on the same codebase, they re-read files, duplicate work, and make conflicting decisions. Tocket fixes this with **shared context files that any agent can read** — no vendor lock-in, no special integrations.
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="docs/assets/tocket-dashboard.png" alt="Tocket CLI Dashboard" width="700" />
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
## The idea in 30 seconds
|
|
16
|
+
|
|
17
|
+
Tocket is a file convention. It adds a `.context/` directory to your repo with markdown files that describe your project's current state, architecture, and progress. Any AI agent that can read files — Claude, Gemini, GPT, Cursor, Copilot — can pick up where the last session left off.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
your-project/
|
|
21
|
+
.context/
|
|
22
|
+
activeContext.md # What's happening right now
|
|
23
|
+
systemPatterns.md # How the codebase is organized
|
|
24
|
+
techContext.md # Stack and build tools
|
|
25
|
+
productContext.md # What the product is and why
|
|
26
|
+
progress.md # What's done, what's next
|
|
27
|
+
TOCKET.md # Protocol rules (for any AI)
|
|
28
|
+
CLAUDE.md # Executor agent config
|
|
29
|
+
GEMINI.md # Architect agent config
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
All files are plain markdown, committed to git, and readable by any tool.
|
|
33
|
+
|
|
34
|
+
## You don't need the CLI
|
|
35
|
+
|
|
36
|
+
The protocol is just files. You can adopt it manually:
|
|
37
|
+
|
|
38
|
+
1. Create a `.context/` directory with `activeContext.md` and `systemPatterns.md`
|
|
39
|
+
2. Add a [`TOCKET.md`](TOCKET.md) to your repo root
|
|
40
|
+
3. Tell your agents to read `.context/` before acting
|
|
41
|
+
|
|
42
|
+
The CLI automates the scaffolding, provides smart defaults, and adds quality-of-life tooling around the protocol.
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Scaffold a new workspace (creates .context/, TOCKET.md, CLAUDE.md, GEMINI.md)
|
|
48
|
+
npx @pedrocivita/tocket init
|
|
49
|
+
|
|
50
|
+
# Or just the essentials (3 files)
|
|
51
|
+
npx @pedrocivita/tocket init --minimal
|
|
52
|
+
|
|
53
|
+
# Or open the interactive dashboard
|
|
54
|
+
npx @pedrocivita/tocket
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That's it. Your repo now has a Memory Bank. Every AI session starts by reading `.context/activeContext.md`.
|
|
58
|
+
|
|
59
|
+
### Safe testing — use a branch
|
|
60
|
+
|
|
61
|
+
Tocket writes files to your repo, but you can try it risk-free on a branch:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git checkout -b test/tocket-setup
|
|
65
|
+
npx @pedrocivita/tocket init
|
|
66
|
+
git add .context/ TOCKET.md CLAUDE.md GEMINI.md .cursorrules
|
|
67
|
+
git commit -m "chore: scaffold Tocket workspace"
|
|
68
|
+
|
|
69
|
+
# Try it out — run some AI sessions, see if you like it
|
|
70
|
+
# Don't like it? Clean up:
|
|
71
|
+
npx @pedrocivita/tocket eject # removes all Tocket files
|
|
72
|
+
# Or just delete the branch:
|
|
73
|
+
git checkout main && git branch -D test/tocket-setup
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
See the [Developer Guide](docs/DEVELOPERS_GUIDE.md) for detailed safe-testing workflows.
|
|
77
|
+
|
|
78
|
+
## Commands
|
|
79
|
+
|
|
80
|
+
| Command | What it does |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `tocket` | Interactive dashboard with guided menu |
|
|
83
|
+
| `tocket init` | Scaffold `.context/`, `TOCKET.md`, and agent configs (auto-detects your stack) |
|
|
84
|
+
| `tocket generate` | Build structured payload XML (auto-fills scope from git) |
|
|
85
|
+
| `tocket sync` | Append session summary + git log to `.context/progress.md` |
|
|
86
|
+
| `tocket validate` | Check if the workspace has a valid Memory Bank |
|
|
87
|
+
| `tocket focus` | Update the Current Focus in `activeContext.md` |
|
|
88
|
+
| `tocket status` | Quick overview: workspace health, branch, focus, agents |
|
|
89
|
+
| `tocket doctor` | Deep workspace diagnostics (content health, git tracking, staleness) |
|
|
90
|
+
| `tocket lint` | Audit `.context/` content quality and suggest improvements |
|
|
91
|
+
| `tocket config` | Manage global settings (`~/.tocketrc.json`) |
|
|
92
|
+
| `tocket eject` | Remove all Tocket files (with confirmation) |
|
|
93
|
+
|
|
94
|
+
### CI-friendly flags
|
|
95
|
+
|
|
96
|
+
Every interactive command has flags for non-interactive use:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Minimal init (3 files instead of 9)
|
|
100
|
+
tocket init --minimal --name myproject --description "My app" --force
|
|
101
|
+
|
|
102
|
+
# Sync without prompt
|
|
103
|
+
tocket sync --summary "Fixed auth bug and added tests"
|
|
104
|
+
|
|
105
|
+
# Generate to stdout or file instead of clipboard
|
|
106
|
+
tocket generate --to stdout
|
|
107
|
+
tocket generate --to payload.xml
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## How it works
|
|
111
|
+
|
|
112
|
+
### Memory Bank
|
|
113
|
+
|
|
114
|
+
The `.context/` directory is the project's shared memory. Agents read it before acting and update it after completing work. Context lives in files, not in chat history.
|
|
115
|
+
|
|
116
|
+
| File | Purpose | Updated |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `activeContext.md` | Current focus, recent changes, open decisions | Every session |
|
|
119
|
+
| `systemPatterns.md` | Architecture patterns and conventions | When patterns change |
|
|
120
|
+
| `techContext.md` | Stack, build tools, critical rules | When stack changes |
|
|
121
|
+
| `productContext.md` | What the product is and why | Rarely |
|
|
122
|
+
| `progress.md` | Milestones and completed work | Per milestone |
|
|
123
|
+
|
|
124
|
+
### Triangulation
|
|
125
|
+
|
|
126
|
+
For complex tasks, Tocket separates planning from implementation:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
Architect (any planning AI) Executor (any coding AI)
|
|
130
|
+
| |
|
|
131
|
+
| 1. Reads .context/ |
|
|
132
|
+
| 2. Analyzes task |
|
|
133
|
+
| 3. Generates <payload> XML |
|
|
134
|
+
|-------- structured handoff ------>|
|
|
135
|
+
| | 4. Reads .context/ + payload
|
|
136
|
+
| | 5. Implements tasks
|
|
137
|
+
| | 6. Updates .context/
|
|
138
|
+
|<-------- status report -----------|
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The Architect doesn't write code. The Executor doesn't make architecture decisions. The payload XML is the contract between them. For simple tasks, a single agent can fill both roles.
|
|
142
|
+
|
|
143
|
+
## Who is this for?
|
|
144
|
+
|
|
145
|
+
- Developers using **multi-agent setups** (Gemini + Claude, Cursor + Copilot, etc.)
|
|
146
|
+
- Teams that want **reproducible AI-assisted development** across sessions
|
|
147
|
+
- Anyone tired of re-explaining project context to AI every time they open a chat
|
|
148
|
+
- Open-source maintainers who want contributors' AI agents to follow project conventions
|
|
149
|
+
|
|
150
|
+
## How is Tocket different?
|
|
151
|
+
|
|
152
|
+
| Tool | What it does | How Tocket differs |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| `.cursorrules` | Single-agent instructions for Cursor | Tocket defines _inter-agent_ protocol, not just single-agent rules |
|
|
155
|
+
| `CLAUDE.md` | Instructions for Claude Code | Tocket generates `CLAUDE.md` as part of a broader multi-agent system |
|
|
156
|
+
| `AGENTS.md` | Codex agent instructions | Same idea for one agent; Tocket coordinates multiple agents |
|
|
157
|
+
| Prompt templates | Static prompts for LLMs | Tocket's Memory Bank evolves with the project; payloads are structured, not freeform |
|
|
158
|
+
|
|
159
|
+
## Configuration
|
|
160
|
+
|
|
161
|
+
Set global defaults so you don't repeat yourself:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Interactive setup
|
|
165
|
+
tocket config
|
|
166
|
+
|
|
167
|
+
# Or use flags (CI-friendly)
|
|
168
|
+
tocket config --author "Your Name" --priority medium --skills "core,lsp"
|
|
169
|
+
|
|
170
|
+
# View current config
|
|
171
|
+
tocket config --show
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Config is stored at `~/.tocketrc.json` and pre-fills author, priority, and skills in all commands.
|
|
175
|
+
|
|
176
|
+
## Documentation
|
|
177
|
+
|
|
178
|
+
| Guide | Description |
|
|
179
|
+
| --- | --- |
|
|
180
|
+
| [Getting Started](docs/GETTING_STARTED.md) | Set up your first Tocket workspace in 5 minutes |
|
|
181
|
+
| [Developer Guide](docs/DEVELOPERS_GUIDE.md) | How to run the Tocket protocol safely in any project |
|
|
182
|
+
| [Tocket Rules](docs/TOCKET_RULES.md) | Complete reference for all protocol rules |
|
|
183
|
+
| [Protocol Spec](TOCKET.md) | The agent-agnostic protocol specification |
|
|
184
|
+
|
|
185
|
+
## Contributing
|
|
186
|
+
|
|
187
|
+
We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) to get started.
|
|
188
|
+
|
|
189
|
+
This project follows a [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
|
@@ -44,7 +44,12 @@ export async function showDashboard(program) {
|
|
|
44
44
|
{ value: "generate", name: "Generate payload" },
|
|
45
45
|
{ value: "sync", name: "Sync progress" },
|
|
46
46
|
{ value: "validate", name: "Validate workspace" },
|
|
47
|
+
{ value: "focus", name: "Update focus" },
|
|
48
|
+
{ value: "status", name: "Workspace status" },
|
|
49
|
+
{ value: "doctor", name: "Run diagnostics" },
|
|
50
|
+
{ value: "lint", name: "Lint context quality" },
|
|
47
51
|
{ value: "config", name: "Configure settings" },
|
|
52
|
+
{ value: "eject", name: "Eject workspace" },
|
|
48
53
|
{ value: "exit", name: "Exit" },
|
|
49
54
|
]
|
|
50
55
|
: [
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
export interface DiagResult {
|
|
3
|
+
icon: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function checkContentHealth(basePath: string): DiagResult[];
|
|
7
|
+
export declare function checkGitTracking(cwd: string): DiagResult[];
|
|
8
|
+
export declare function checkStaleness(basePath: string): DiagResult | null;
|
|
9
|
+
export declare function registerDoctorCommand(program: Command): void;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { success as themePass, warn as themeWarn, error as themeFail, heading, dim } from "../utils/theme.js";
|
|
5
|
+
import { isGitRepo } from "../utils/git.js";
|
|
6
|
+
const PASS = themePass("");
|
|
7
|
+
const WARN = themeWarn("");
|
|
8
|
+
const FAIL = themeFail("");
|
|
9
|
+
export function checkContentHealth(basePath) {
|
|
10
|
+
const results = [];
|
|
11
|
+
// activeContext.md: non-empty Current Focus
|
|
12
|
+
const acPath = join(basePath, ".context", "activeContext.md");
|
|
13
|
+
if (existsSync(acPath)) {
|
|
14
|
+
const content = readFileSync(acPath, "utf-8");
|
|
15
|
+
const focusMatch = content.match(/## Current Focus\s*\n+([\s\S]*?)(?=\n##|\n*$)/);
|
|
16
|
+
const focusBody = focusMatch?.[1]?.trim() ?? "";
|
|
17
|
+
if (!focusBody || focusBody.startsWith("_") || focusBody.includes("No active tasks")) {
|
|
18
|
+
results.push({ icon: WARN, message: "activeContext.md has no meaningful Current Focus" });
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
results.push({ icon: PASS, message: "activeContext.md has an active focus" });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// systemPatterns.md: has at least one convention
|
|
25
|
+
const spPath = join(basePath, ".context", "systemPatterns.md");
|
|
26
|
+
if (existsSync(spPath)) {
|
|
27
|
+
const content = readFileSync(spPath, "utf-8");
|
|
28
|
+
const hasConvention = content.includes("- ") || content.includes("| ");
|
|
29
|
+
if (hasConvention) {
|
|
30
|
+
results.push({ icon: PASS, message: "systemPatterns.md has documented conventions" });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
results.push({ icon: WARN, message: "systemPatterns.md has no conventions documented" });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// TOCKET.md: contains payload or version keyword
|
|
37
|
+
const tocketPath = join(basePath, "TOCKET.md");
|
|
38
|
+
if (existsSync(tocketPath)) {
|
|
39
|
+
const content = readFileSync(tocketPath, "utf-8");
|
|
40
|
+
if (content.includes("payload") || content.includes("version")) {
|
|
41
|
+
results.push({ icon: PASS, message: "TOCKET.md contains protocol keywords" });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
results.push({ icon: WARN, message: "TOCKET.md may not be a valid protocol spec" });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Agent configs reference .context/
|
|
48
|
+
const agentFiles = ["CLAUDE.md", "GEMINI.md"];
|
|
49
|
+
for (const af of agentFiles) {
|
|
50
|
+
const afPath = join(basePath, af);
|
|
51
|
+
if (existsSync(afPath)) {
|
|
52
|
+
const content = readFileSync(afPath, "utf-8");
|
|
53
|
+
if (content.includes(".context/") || content.includes(".context\\")) {
|
|
54
|
+
results.push({ icon: PASS, message: `${af} references .context/` });
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
results.push({ icon: WARN, message: `${af} does not reference .context/` });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
export function checkGitTracking(cwd) {
|
|
64
|
+
const results = [];
|
|
65
|
+
if (!isGitRepo(cwd)) {
|
|
66
|
+
results.push({ icon: WARN, message: "Not a git repository" });
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
// Check if .context/ is in .gitignore
|
|
70
|
+
try {
|
|
71
|
+
const output = execSync("git check-ignore .context/", {
|
|
72
|
+
cwd,
|
|
73
|
+
encoding: "utf-8",
|
|
74
|
+
}).trim();
|
|
75
|
+
if (output) {
|
|
76
|
+
results.push({ icon: FAIL, message: ".context/ is in .gitignore (should be tracked)" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// exit code 1 = not ignored (good)
|
|
81
|
+
results.push({ icon: PASS, message: ".context/ is not gitignored" });
|
|
82
|
+
}
|
|
83
|
+
// Check for uncommitted .context/ changes
|
|
84
|
+
try {
|
|
85
|
+
const output = execSync("git status --porcelain .context/", {
|
|
86
|
+
cwd,
|
|
87
|
+
encoding: "utf-8",
|
|
88
|
+
}).trim();
|
|
89
|
+
if (output) {
|
|
90
|
+
const count = output.split("\n").length;
|
|
91
|
+
results.push({ icon: WARN, message: `${count} uncommitted change(s) in .context/` });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
results.push({ icon: PASS, message: ".context/ files are committed" });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// git error — skip
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
export function checkStaleness(basePath) {
|
|
103
|
+
const acPath = join(basePath, ".context", "activeContext.md");
|
|
104
|
+
if (!existsSync(acPath))
|
|
105
|
+
return null;
|
|
106
|
+
const stats = statSync(acPath);
|
|
107
|
+
const daysSinceModified = Math.floor((Date.now() - stats.mtimeMs) / (1000 * 60 * 60 * 24));
|
|
108
|
+
if (daysSinceModified > 7) {
|
|
109
|
+
return {
|
|
110
|
+
icon: WARN,
|
|
111
|
+
message: `activeContext.md last modified ${daysSinceModified} days ago (may be stale)`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
export function registerDoctorCommand(program) {
|
|
117
|
+
program
|
|
118
|
+
.command("doctor")
|
|
119
|
+
.description("Deep diagnostic of a Tocket workspace")
|
|
120
|
+
.action(() => {
|
|
121
|
+
const cwd = process.cwd();
|
|
122
|
+
const results = [];
|
|
123
|
+
let passCount = 0;
|
|
124
|
+
let warnCount = 0;
|
|
125
|
+
let failCount = 0;
|
|
126
|
+
console.log(heading("\nTocket Doctor\n"));
|
|
127
|
+
// 1. File existence (same as validate)
|
|
128
|
+
const contextDir = join(cwd, ".context");
|
|
129
|
+
if (!existsSync(contextDir)) {
|
|
130
|
+
results.push({ icon: FAIL, message: ".context/ directory missing" });
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
results.push({ icon: PASS, message: ".context/ directory found" });
|
|
134
|
+
const requiredFiles = ["activeContext.md", "systemPatterns.md"];
|
|
135
|
+
const optionalFiles = ["techContext.md", "productContext.md", "progress.md"];
|
|
136
|
+
for (const f of requiredFiles) {
|
|
137
|
+
if (existsSync(join(contextDir, f))) {
|
|
138
|
+
results.push({ icon: PASS, message: `.context/${f} found` });
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
results.push({ icon: FAIL, message: `.context/${f} missing (required)` });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const f of optionalFiles) {
|
|
145
|
+
if (existsSync(join(contextDir, f))) {
|
|
146
|
+
results.push({ icon: PASS, message: `.context/${f} found` });
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
results.push({ icon: WARN, message: `.context/${f} missing (optional)` });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (existsSync(join(cwd, "TOCKET.md"))) {
|
|
154
|
+
results.push({ icon: PASS, message: "TOCKET.md found" });
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
results.push({ icon: FAIL, message: "TOCKET.md missing (required)" });
|
|
158
|
+
}
|
|
159
|
+
// 2. Content health
|
|
160
|
+
results.push(...checkContentHealth(cwd));
|
|
161
|
+
// 3. Staleness
|
|
162
|
+
const stale = checkStaleness(cwd);
|
|
163
|
+
if (stale)
|
|
164
|
+
results.push(stale);
|
|
165
|
+
// 4. Git tracking
|
|
166
|
+
results.push(...checkGitTracking(cwd));
|
|
167
|
+
// Print results
|
|
168
|
+
for (const r of results) {
|
|
169
|
+
console.log(` ${r.icon} ${r.message}`);
|
|
170
|
+
if (r.icon === PASS)
|
|
171
|
+
passCount++;
|
|
172
|
+
else if (r.icon === WARN)
|
|
173
|
+
warnCount++;
|
|
174
|
+
else
|
|
175
|
+
failCount++;
|
|
176
|
+
}
|
|
177
|
+
console.log("");
|
|
178
|
+
console.log(dim(` ${passCount} passed, ${warnCount} warnings, ${failCount} failures`));
|
|
179
|
+
console.log("");
|
|
180
|
+
if (failCount > 0) {
|
|
181
|
+
console.log(themeFail("Workspace has issues.") + " Run tocket init to scaffold missing files.");
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
}
|
|
184
|
+
else if (warnCount > 0) {
|
|
185
|
+
console.log(themeWarn("Workspace is functional but has warnings."));
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
console.log(themePass("Workspace is in great shape."));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -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;
|