janissary 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -20
- package/ai/tasks/build-a-feature.md +12 -12
- package/ai/tasks/fix-a-bug.md +191 -0
- package/ai/tasks/fix-a-small-issue.md +34 -21
- package/ai/tasks/improve-codebase.md +3 -5
- package/ai/tasks/improve-modularity.md +0 -2
- package/ai/tasks/improve-namespacing.md +0 -2
- package/ai/tasks/improve-plan.md +8 -8
- package/ai/tasks/improve-security.md +0 -2
- package/ai/tasks/improve-style.md +0 -2
- package/ai/tasks/improve-test-coverage.md +0 -2
- package/ai/tasks/merge-change-to-master.md +0 -2
- package/ai/tasks/open-feature-pull-request.md +0 -2
- package/ai/tasks/plan-ready-features.md +17 -17
- package/ai/tasks/prepare-workspace.md +0 -2
- package/ai/tasks/quick-commit.md +0 -2
- package/ai/tasks/reduce-complexity.md +0 -2
- package/ai/tasks/remove-deadcode.md +0 -2
- package/ai/tasks/remove-duplication.md +0 -2
- package/ai/tasks/update-package.md +0 -2
- package/dist/agent/commands.js +1 -1
- package/dist/agent-names.js +21 -0
- package/dist/buffer.js +2 -2
- package/dist/cli-args.js +8 -2
- package/dist/command-manager.js +6 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/schedules.js +17 -0
- package/dist/commands.js +1 -1
- package/dist/controller.js +12 -2
- package/dist/database/index.js +1 -30
- package/dist/database/primer.js +30 -0
- package/dist/file-tree/args.js +36 -0
- package/dist/file-tree/git-refresh.js +33 -0
- package/dist/file-tree/index.js +82 -0
- package/dist/file-tree/manager.js +252 -0
- package/dist/file-tree/moves.js +26 -0
- package/dist/file-tree/open.js +56 -0
- package/dist/file-tree/rebuild.js +22 -0
- package/dist/file-tree/watch.js +27 -0
- package/dist/harness/auto-approve.js +1 -1
- package/dist/harness/index.js +23 -3
- package/dist/harness/manager.js +44 -6
- package/dist/harness/models.js +20 -1
- package/dist/harness/scratch-dir.js +10 -0
- package/dist/instance-lock.js +1 -1
- package/dist/main.js +4 -0
- package/dist/message-handler.js +8 -0
- package/dist/notifications.js +2 -2
- package/dist/profile/agent-opener.js +1 -36
- package/dist/profile/harness-schedule.js +42 -0
- package/dist/profile/manager.js +27 -6
- package/dist/profile-reserved-files.js +69 -0
- package/dist/profiles.js +4 -61
- package/dist/pseudoterminal-manager.js +4 -2
- package/dist/pty.js +2 -2
- package/dist/sandbox/paths.js +1 -0
- package/dist/schedule/manager.js +17 -0
- package/dist/schedules-tab.js +23 -0
- package/dist/state-event.js +7 -4
- package/dist/tab/creators.js +13 -28
- package/dist/tab/file-registry.js +20 -0
- package/dist/tab/index.js +8 -0
- package/dist/tab/manager.js +26 -41
- package/dist/tab/openers.js +4 -1
- package/dist/tab/queue-commands.js +25 -0
- package/dist/tab/rehydrate-state.js +13 -0
- package/dist/tab/transcript-ops.js +15 -0
- package/dist/tab/unique-labels.js +27 -0
- package/dist/tab/view.js +2 -1
- package/dist/tasks.js +29 -9
- package/package.json +7 -1
- package/profiles/claude/_files.json +3 -0
- package/profiles/claude/_monitors.json +6 -0
- package/profiles/claude/_notifications.json +3 -0
- package/profiles/claude/claude.json +7 -0
- package/profiles/coding/execute.json +9 -0
- package/profiles/coding/plan.json +9 -0
- package/profiles/coding/review.json +19 -0
- package/profiles/small-fix/opencode.json +11 -0
- package/scripts/release.mjs +1 -1
- package/web/dist/assets/index-CwBP-03m.js +2145 -0
- package/web/dist/assets/index-DxzfdL9V.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DKEtQh6C.css +0 -1
- package/web/dist/assets/index-Dz5j8yq-.js +0 -1066
|
@@ -10,8 +10,6 @@ Every step is a script in `scripts/pr-*.sh`, invoked through the script runner.
|
|
|
10
10
|
|
|
11
11
|
**Always run scripts in the foreground.** Never use `run_in_background` — each script must complete and return its exit code before the next step begins.
|
|
12
12
|
|
|
13
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to do any part of this task on your behalf.
|
|
14
|
-
|
|
15
13
|
**Do not ask the user for input at any point.** Make all decisions autonomously — branch names, commit messages, PR titles and bodies. The only valid reason to stop is an unresolvable error.
|
|
16
14
|
|
|
17
15
|
---
|
|
@@ -8,8 +8,6 @@ Your job: take the uncommitted work in this repository, package it into a well-d
|
|
|
8
8
|
|
|
9
9
|
**Run everything synchronously, in the foreground.** Never use `run_in_background`, `&`, or otherwise start a background process (dev servers, watchers, long-lived processes) — every command must finish and return its exit code before you move to the next step.
|
|
10
10
|
|
|
11
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to research, explore, or implement any part of this task on your behalf.
|
|
12
|
-
|
|
13
11
|
## The PR description
|
|
14
12
|
|
|
15
13
|
The PR body is the deliverable. It must be **thorough and self-contained** — a reviewer should understand the feature without reading the code. Write it to `./temp/pr-body.md`, then pass that file to the create-PR script.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Plan Ready Features
|
|
2
2
|
|
|
3
|
-
Your job: walk the `## ready` section of
|
|
3
|
+
Your job: walk the `## ready` section of `./product/backlog/features.md` one feature at a time, interview the user with product-focused questions to pin down scope and behavior, and write a draft plan for each into `./product/plans/draft/`. You produce plan documents only — you never write source code.
|
|
4
|
+
|
|
5
|
+
**Project `./product/` directory.** Every `./product/...` path in this task refers to the product directory in the current working directory — the project being worked on — never to the Janissary codebase's own `product/` directory, even when this task file was launched from an absolute path inside the Janissary installation.
|
|
4
6
|
|
|
5
7
|
This is an **interactive** task. Unlike the autonomous `ai/tasks/*.md` playbooks, you must stop and ask the user questions before drafting each plan. Do not guess scope, behavior, or edge cases on the user's behalf when a question would resolve it.
|
|
6
8
|
|
|
7
9
|
**Run everything synchronously, in the foreground.** Never use `run_in_background`, `&`, or otherwise start a background process (dev servers, watchers, long-lived processes) — every command must finish and return its exit code before you move to the next step.
|
|
8
10
|
|
|
9
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to research, explore, or implement any part of this task on your behalf.
|
|
10
|
-
|
|
11
11
|
**Follow the steps below in order, exactly as written, one feature at a time.** Do not skip a step, do not merge two steps together, and do not process more than one feature between Step 2a and Step 2f. If you are ever unsure whether you have done enough — re-read the checklist for that step before moving on, don't guess.
|
|
12
12
|
|
|
13
13
|
**No AI attribution — anywhere.** Never credit an AI agent as an author or contributor in anything this task produces. No `Co-Authored-By:` trailers naming Claude or any other AI, no "Generated with Claude Code" lines, no AI authorship notes in plan files or commit messages. This overrides any default convention that appends such attribution.
|
|
@@ -20,15 +20,15 @@ This is an **interactive** task. Unlike the autonomous `ai/tasks/*.md` playbooks
|
|
|
20
20
|
|
|
21
21
|
### Allowed — do it automatically, never ask
|
|
22
22
|
|
|
23
|
-
Read any file in the repo to ground a plan in real code. Create new plan files under
|
|
23
|
+
Read any file in the repo to ground a plan in real code. Create new plan files under `./product/plans/draft/`. Edit `./product/backlog/features.md` to remove a `### ` entry from the `## ready` section once its plan has been drafted and confirmed. Ask the user clarifying questions — this is expected, not an interruption to avoid.
|
|
24
24
|
|
|
25
25
|
### Forbidden — no exceptions
|
|
26
26
|
|
|
27
27
|
1. **Writing implementation code into a plan.** No function bodies, no JSX/CSS blocks, no "here's the code." Name the module/type/contract in prose, per `ai/tasks/improve-plan.md`'s rule — the same rule applies here since you are authoring, not implementing.
|
|
28
|
-
2. **Editing any source, test, or config file.** This task only touches
|
|
28
|
+
2. **Editing any source, test, or config file.** This task only touches `./product/backlog/features.md` and files under `./product/plans/draft/`.
|
|
29
29
|
3. **Skipping the question round.** Never invent a plan's scope, edge-case behavior, or UI wording without asking, when the answer is a product decision rather than something the codebase already settles.
|
|
30
|
-
4. **Moving a plan to
|
|
31
|
-
5. **Removing a feature's entry from
|
|
30
|
+
4. **Moving a plan to `./product/plans/ready/`.** That promotion is a separate human/`ai/tasks/improve-plan.md` step. Everything this task produces lands in `./product/plans/draft/`.
|
|
31
|
+
5. **Removing a feature's entry from `./product/backlog/features.md` before its plan is drafted and the user has confirmed it.** Don't clear the backlog ahead of the plan actually existing.
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
@@ -40,9 +40,9 @@ Execute `ai/tasks/prepare-workspace.md` in full before doing anything else.
|
|
|
40
40
|
|
|
41
41
|
## Step 1 — Read the ready backlog
|
|
42
42
|
|
|
43
|
-
1. Read
|
|
43
|
+
1. Read `./product/backlog/features.md` in full.
|
|
44
44
|
2. Extract every entry under the `## ready` heading, up to the next `## ` heading. Each entry is a `### <title>` followed by its description body. Copy titles verbatim — do not paraphrase or shorten them.
|
|
45
|
-
3. If the `## ready` section is empty, report "No ready features in
|
|
45
|
+
3. If the `## ready` section is empty, report "No ready features in `./product/backlog/features.md`" and stop.
|
|
46
46
|
4. Write down the exact ordered list of feature titles as a numbered list in your own working notes, e.g.:
|
|
47
47
|
```
|
|
48
48
|
1. profile launcher
|
|
@@ -64,14 +64,14 @@ Before starting each feature, say in one sentence which feature (by number and t
|
|
|
64
64
|
Do exactly this, in order, before asking any question:
|
|
65
65
|
|
|
66
66
|
1. Grep the repo for the feature's key nouns (e.g. for "profile launcher", grep for `profile` and `picker`) to find existing related code.
|
|
67
|
-
2. Read any spec file under
|
|
67
|
+
2. Read any spec file under `./product/specs/` whose name matches the feature's topic.
|
|
68
68
|
3. If the feature's description names an existing feature as a comparison point (e.g. "just like action picker"), open that feature's implementation and read it.
|
|
69
69
|
|
|
70
70
|
Stop after these three checks — this is reconnaissance, not a design phase. Do not spend more than a few minutes here. If you find nothing related, that is a valid outcome; say so and move on to 2b.
|
|
71
71
|
|
|
72
72
|
### 2b. Decide whether this feature needs implementation questions
|
|
73
73
|
|
|
74
|
-
Answer these four yes/no checks about the feature, using only what the feature's
|
|
74
|
+
Answer these four yes/no checks about the feature, using only what the feature's `./product/backlog/features.md` text says plus what you found in 2a:
|
|
75
75
|
|
|
76
76
|
1. Does it require a **new subsystem** (not an extension of one that already exists)?
|
|
77
77
|
2. Does it require a **new client/server protocol message or RPC**?
|
|
@@ -91,7 +91,7 @@ Ask the user questions using `AskUserQuestion`, one call per round, **maximum 4
|
|
|
91
91
|
- **Scope boundary** — what is explicitly *not* included in this version.
|
|
92
92
|
- **Naming/wording** — any user-visible text (labels, commands, messages) the plan needs to fix precisely.
|
|
93
93
|
|
|
94
|
-
Skip a category only if the feature's existing description in
|
|
94
|
+
Skip a category only if the feature's existing description in `./product/backlog/features.md` already answers it unambiguously — quote that text back to the user as your understanding rather than silently assuming it.
|
|
95
95
|
|
|
96
96
|
Every question must offer concrete options grounded in what you found in Step 2a, rather than an open-ended "what should happen?". If a question has no natural small set of options, still propose your best-guess default as one of the options so the user can simply confirm it.
|
|
97
97
|
|
|
@@ -108,7 +108,7 @@ If any box is unchecked, ask one more round (max 4 questions) targeting only the
|
|
|
108
108
|
|
|
109
109
|
### 2d. Draft the plan
|
|
110
110
|
|
|
111
|
-
Write
|
|
111
|
+
Write `./product/plans/draft/<slug>.md`, where `<slug>` is a kebab-case name derived from the feature's `### ` title (lowercase, spaces to hyphens, strip punctuation — e.g. "profile launcher" → `profile-launcher.md`). Follow the house style used in `./product/plans/ready/` (skim an existing one, e.g. `./product/plans/ready/monitor-page-tab-content-feed.md`, for shape). The plan file must contain, in this order, every one of these sections — do not omit any:
|
|
112
112
|
|
|
113
113
|
1. `# <Feature name>` title.
|
|
114
114
|
2. A short summary paragraph: what the feature does and why, folding in what the user told you in 2c.
|
|
@@ -130,11 +130,11 @@ Before moving on, check the plan file against this list:
|
|
|
130
130
|
|
|
131
131
|
### 2e. Confirm with the user
|
|
132
132
|
|
|
133
|
-
Show the user a brief summary of the drafted plan (goal, key decisions, out-of-scope boundary) and confirm it matches what they described. If they want changes, edit the plan and show the summary again — repeat until confirmed. If they want to abandon the feature entirely, delete
|
|
133
|
+
Show the user a brief summary of the drafted plan (goal, key decisions, out-of-scope boundary) and confirm it matches what they described. If they want changes, edit the plan and show the summary again — repeat until confirmed. If they want to abandon the feature entirely, delete `./product/plans/draft/<slug>.md` if you already wrote it, leave the entry in `./product/backlog/features.md` untouched, note it as skipped, and move to the next feature in the queue.
|
|
134
134
|
|
|
135
135
|
### 2f. Remove the entry from the backlog
|
|
136
136
|
|
|
137
|
-
Once confirmed, edit
|
|
137
|
+
Once confirmed, edit `./product/backlog/features.md` to remove that feature's `### ` entry (title and body) from the `## ready` section. Leave every other section, heading, and entry byte-for-byte untouched — do not reformat or reflow surrounding text.
|
|
138
138
|
|
|
139
139
|
Only after this step is complete, return to the top of Step 2 for the next feature in the queue.
|
|
140
140
|
|
|
@@ -146,9 +146,9 @@ After all ready features are processed (planned or explicitly skipped per 2e), g
|
|
|
146
146
|
|
|
147
147
|
```
|
|
148
148
|
Processed: N ready features
|
|
149
|
-
Planned: product/plans/draft/<file> — <one-line goal> (repeat per feature)
|
|
149
|
+
Planned: ./product/plans/draft/<file> — <one-line goal> (repeat per feature)
|
|
150
150
|
Skipped: <feature title> — <why, if any were abandoned>
|
|
151
|
-
Backlog: product/backlog/features.md updated, N entries removed from ## ready
|
|
151
|
+
Backlog: ./product/backlog/features.md updated, N entries removed from ## ready
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
Keep it brief. Done.
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
**Run everything synchronously, in the foreground.** Never use `run_in_background`, `&`, or otherwise start a background process (dev servers, watchers, long-lived processes) — every command must finish and return its exit code before you move to the next step.
|
|
6
6
|
|
|
7
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to research, explore, or implement any part of this task on your behalf.
|
|
8
|
-
|
|
9
7
|
---
|
|
10
8
|
|
|
11
9
|
## Step 1 — Pull latest changes
|
package/ai/tasks/quick-commit.md
CHANGED
|
@@ -6,8 +6,6 @@ Your job: package the current working-tree changes into **one** commit on the **
|
|
|
6
6
|
|
|
7
7
|
**Run scripts in the foreground** (never `run_in_background`) — each must finish and return its exit code before the next step. **Make all decisions autonomously** (commit type, scope, message); the only reason to stop is an unresolvable error.
|
|
8
8
|
|
|
9
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to do any part of this task on your behalf.
|
|
10
|
-
|
|
11
9
|
---
|
|
12
10
|
|
|
13
11
|
## Step 1 — Compose a Conventional Commits message
|
|
@@ -8,8 +8,6 @@ This task is about complexity, not file size: **all code you touch must stay in
|
|
|
8
8
|
|
|
9
9
|
**Run everything synchronously, in the foreground.** Never use `run_in_background`, `&`, or otherwise start a background process (dev servers, watchers, long-lived processes) — every command must finish and return its exit code before you move to the next step.
|
|
10
10
|
|
|
11
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to research, explore, or implement any part of this task on your behalf.
|
|
12
|
-
|
|
13
11
|
Refactoring edits real code, so the rule is simple: **the tests must pass before you start and still pass after. If you cannot keep them passing, put the code back the way it was.**
|
|
14
12
|
|
|
15
13
|
Do the steps below **in order**. Do not skip steps. Do not invent your own process.
|
|
@@ -8,8 +8,6 @@ The rule is simple: **the compiler, the linter, and the tests must be green befo
|
|
|
8
8
|
|
|
9
9
|
**Run everything synchronously, in the foreground.** Never use `run_in_background`, `&`, or otherwise start a background process (dev servers, watchers, long-lived processes) — every command must finish and return its exit code before you move to the next step.
|
|
10
10
|
|
|
11
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to research, explore, or implement any part of this task on your behalf.
|
|
12
|
-
|
|
13
11
|
**Shell hygiene:** run every command on its own line — no `&&` chaining, no `; echo "Exit code: $?"` suffixes, no subshell captures. The exit code and output are visible in the tool result. To run a project script, always use `./scripts/run.mjs <name>` — never call `node scripts/<name>.mjs` directly.
|
|
14
12
|
|
|
15
13
|
Do the steps below **in order**. Do not skip steps. Do not invent your own process.
|
|
@@ -8,8 +8,6 @@ Deduplication edits real code that other files may import, so the rule is simple
|
|
|
8
8
|
|
|
9
9
|
**Run everything synchronously, in the foreground.** Never use `run_in_background`, `&`, or otherwise start a background process (dev servers, watchers, long-lived processes) — every command must finish and return its exit code before you move to the next step.
|
|
10
10
|
|
|
11
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to research, explore, or implement any part of this task on your behalf.
|
|
12
|
-
|
|
13
11
|
**Shell hygiene:** run every command on its own line — no `&&` chaining, no `; echo "Exit code: $?"` suffixes, no subshell captures. The exit code and output are visible in the tool result. To run a project script, always use `./scripts/run.mjs <name>` — never call `node scripts/<name>.mjs` directly.
|
|
14
12
|
|
|
15
13
|
Do the steps below **in order**. Do not skip steps. Do not invent your own process.
|
|
@@ -8,8 +8,6 @@ Your job: update **one** npm package to the latest version its existing `package
|
|
|
8
8
|
|
|
9
9
|
**Run everything synchronously, in the foreground.** Never use `run_in_background`, `&`, or otherwise start a background process (dev servers, watchers, long-lived processes) — every command must finish and return its exit code before you move to the next step.
|
|
10
10
|
|
|
11
|
-
**No subagents, no background agents.** Do every step yourself — never launch a subagent (Task/Agent tool, `fork`, or otherwise) to research, explore, or implement any part of this task on your behalf.
|
|
12
|
-
|
|
13
11
|
**Run autonomously.** This task runs unattended — do not ask the user questions or wait for feedback at any step. Only stop early for the conditions explicitly listed below.
|
|
14
12
|
|
|
15
13
|
The rule is simple: **the compiler, the linter, and the tests must be green before you start and green again after you finish.** If not green before you start, stop and tell the user. If you can't get it green again after two fix attempts, put the package back and stop.
|
package/dist/agent/commands.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import defaultNames from '../agent-names.json' with { type: 'json' };
|
|
4
|
+
export let agentNames = defaultNames;
|
|
5
|
+
// Reads `.janissary/agent-names.json` from the project directory and, when present and valid,
|
|
6
|
+
// uses it in place of the bundled name list for the rest of this run — the same load-with-fallback
|
|
7
|
+
// contract as `config.ts`'s `.janissary/config.json`.
|
|
8
|
+
export function loadAgentNames(projectDirectory) {
|
|
9
|
+
const overridePath = path.join(projectDirectory, '.janissary', 'agent-names.json');
|
|
10
|
+
if (!existsSync(overridePath)) {
|
|
11
|
+
agentNames = defaultNames;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
agentNames = JSON.parse(readFileSync(overridePath, 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
process.stderr.write('warning: .janissary/agent-names.json is invalid JSON — using the bundled name list\n');
|
|
19
|
+
agentNames = defaultNames;
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/buffer.js
CHANGED
|
@@ -27,13 +27,13 @@ export function formatMessageContent(entry, parts) {
|
|
|
27
27
|
const kind = entry.msgKind ?? 'info';
|
|
28
28
|
const lines = [];
|
|
29
29
|
if (kind === 'response') {
|
|
30
|
-
lines.push({ type: 'message', text: '', from: entry.from, fromColor: entry.fromColor, msgKind: 'response' });
|
|
30
|
+
lines.push({ type: 'message', text: '', from: entry.from, fromColor: entry.fromColor, msgKind: 'response', ...(entry.openFile && { openFile: entry.openFile }) });
|
|
31
31
|
for (const line of parts) {
|
|
32
32
|
lines.push({ type: 'output', text: expandTabs(line), fromColor: entry.fromColor });
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
36
|
-
lines.push({ type: 'message', text: expandTabs(parts[0] ?? ''), from: entry.from, fromColor: entry.fromColor, msgKind: kind });
|
|
36
|
+
lines.push({ type: 'message', text: expandTabs(parts[0] ?? ''), from: entry.from, fromColor: entry.fromColor, msgKind: kind, ...(entry.openFile && { openFile: entry.openFile }) });
|
|
37
37
|
for (const extra of parts.slice(1)) {
|
|
38
38
|
lines.push({ type: 'output', text: expandTabs(extra), fromColor: entry.fromColor });
|
|
39
39
|
}
|
package/dist/cli-args.js
CHANGED
|
@@ -73,8 +73,14 @@ Environment:
|
|
|
73
73
|
JANUS_DEBUG=1 print stack traces on failure
|
|
74
74
|
`;
|
|
75
75
|
}
|
|
76
|
-
|
|
76
|
+
function readPackageInfo() {
|
|
77
77
|
const packagePath = path.join(import.meta.dirname, '..', 'package.json');
|
|
78
|
-
|
|
78
|
+
return JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
79
|
+
}
|
|
80
|
+
export function appVersion() {
|
|
81
|
+
const pkg = readPackageInfo();
|
|
79
82
|
return `${pkg.name} ${pkg.version}`;
|
|
80
83
|
}
|
|
84
|
+
export function appVersionNumber() {
|
|
85
|
+
return readPackageInfo().version;
|
|
86
|
+
}
|
package/dist/command-manager.js
CHANGED
|
@@ -79,6 +79,12 @@ export class CommandManager {
|
|
|
79
79
|
}
|
|
80
80
|
run(input, label, index) {
|
|
81
81
|
if (/^harness\b/i.test(input)) {
|
|
82
|
+
// Bare `harness` (no args) opens the launch dialog instead of erroring; no transcript line is
|
|
83
|
+
// recorded, so nothing is appended ahead of the dialog. Every other `harness …` form runs.
|
|
84
|
+
if (input.trim().toLowerCase() === 'harness') {
|
|
85
|
+
this.managers.harness.openLaunchDialog();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
82
88
|
this.managers.tab.append(label, { input, output: '' });
|
|
83
89
|
const error = this.managers.harness.run(input);
|
|
84
90
|
if (error)
|
package/dist/commands/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { command as rename } from './rename.js';
|
|
|
22
22
|
import { command as search } from './search.js';
|
|
23
23
|
import { command as files } from './files.js';
|
|
24
24
|
import { command as notifications } from './notifications.js';
|
|
25
|
+
import { command as schedules } from './schedules.js';
|
|
25
26
|
import { command as notify } from './notify.js';
|
|
26
27
|
import { command as syntax } from './syntax.js';
|
|
27
28
|
import { command as theme } from './theme.js';
|
|
@@ -52,6 +53,7 @@ export const commands = [
|
|
|
52
53
|
search,
|
|
53
54
|
files,
|
|
54
55
|
notifications,
|
|
56
|
+
schedules,
|
|
55
57
|
notify,
|
|
56
58
|
syntax,
|
|
57
59
|
theme,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { openSchedulesTab } from '../schedules-tab.js';
|
|
2
|
+
// `schedules` opens (or focuses) the singleton schedules tab — the aggregated, next-to-run-ordered
|
|
3
|
+
// list of every scheduled command across all tabs. `schedules left` / `schedules right` dock it into
|
|
4
|
+
// that sidebar, mirroring `notifications [left|right]`. Bare `schedules` on a docked tab undocks it
|
|
5
|
+
// back to center and makes it active. The tab is a singleton — a second invocation reuses it. This is
|
|
6
|
+
// distinct from the singular `schedule` command, which creates and manages a tab's own timers.
|
|
7
|
+
export const command = {
|
|
8
|
+
name: 'schedules',
|
|
9
|
+
match: (command_) => /^schedules\b/i.test(command_),
|
|
10
|
+
run: (command_, tab, managers) => {
|
|
11
|
+
managers.tab.append(tab.label, { input: command_, output: '' });
|
|
12
|
+
const rest = command_.replace(/^schedules\b\s*/i, '');
|
|
13
|
+
const keyword = /^(left|right)\b/i.exec(rest);
|
|
14
|
+
const dock = keyword ? keyword[1].toLowerCase() : undefined;
|
|
15
|
+
openSchedulesTab(managers, dock);
|
|
16
|
+
},
|
|
17
|
+
};
|
package/dist/commands.js
CHANGED
|
@@ -67,4 +67,4 @@ export const getOutput = (command) => {
|
|
|
67
67
|
return null;
|
|
68
68
|
return `Unknown command: "${trimmed}". Type "help" for available commands.`;
|
|
69
69
|
};
|
|
70
|
-
export {
|
|
70
|
+
export { agentNames } from './agent-names.js';
|
package/dist/controller.js
CHANGED
|
@@ -11,7 +11,7 @@ import { ScheduleManager } from './schedule/manager.js';
|
|
|
11
11
|
import { ProfileManager } from './profile/manager.js';
|
|
12
12
|
import { ConnectionManager } from './connection/manager.js';
|
|
13
13
|
import { OpenFileManager } from './open-file-manager.js';
|
|
14
|
-
import { FileTreeManager } from './file-tree
|
|
14
|
+
import { FileTreeManager } from './file-tree/manager.js';
|
|
15
15
|
import * as fileTreeRpc from './controller-file-tree.js';
|
|
16
16
|
import { wireControllerEvents } from './controller-events.js';
|
|
17
17
|
import { EditorWatchManager } from './editor/watch-manager.js';
|
|
@@ -63,7 +63,7 @@ export class Controller {
|
|
|
63
63
|
this.managers.schedule.set(s.name, s.schedule); });
|
|
64
64
|
}
|
|
65
65
|
view() {
|
|
66
|
-
return this.managers.tab.view((l) => this.managers.connection.connectionsFor(l), (l) => this.managers.acp.label(l), (l) => this.managers.schedule.view(l));
|
|
66
|
+
return this.managers.tab.view((l) => this.managers.connection.connectionsFor(l), (l) => this.managers.acp.label(l), (l) => this.managers.schedule.view(l), this.managers.schedule.aggregatedView());
|
|
67
67
|
}
|
|
68
68
|
routeView() {
|
|
69
69
|
return this.managers.command.routeView();
|
|
@@ -71,6 +71,12 @@ export class Controller {
|
|
|
71
71
|
chooseRoute(index) {
|
|
72
72
|
this.managers.command.chooseRoute(index);
|
|
73
73
|
}
|
|
74
|
+
harnessLaunchView() {
|
|
75
|
+
return this.managers.harness.harnessLaunchView();
|
|
76
|
+
}
|
|
77
|
+
closeHarnessLaunch() {
|
|
78
|
+
this.managers.harness.closeLaunchDialog();
|
|
79
|
+
}
|
|
74
80
|
dispatch(text) {
|
|
75
81
|
this.managers.command.dispatch(text);
|
|
76
82
|
}
|
|
@@ -168,6 +174,10 @@ export class Controller {
|
|
|
168
174
|
openFileNavigatorFor(label) {
|
|
169
175
|
fileTreeRpc.openFileNavigatorFor(this.managers, label);
|
|
170
176
|
}
|
|
177
|
+
// Launch a new agent tab rooted at the named tab's cwd — the ➕ metadata-row button.
|
|
178
|
+
launchAgentFor(label) {
|
|
179
|
+
this.managers.profile.newAgentAt(label);
|
|
180
|
+
}
|
|
171
181
|
// Tab-completion for the command line (reuses the shared `completeCommandLine`): filesystem
|
|
172
182
|
// paths against the active tab's cwd, `msg`/`broadcast` agent names, `connection close` targets,
|
|
173
183
|
// and `browser` subcommands / window ids.
|
package/dist/database/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { databaseFileExists as databaseFileExists, listDatabaseFiles, removeData
|
|
|
2
2
|
import { parseDatabaseCommand } from './parsing.js';
|
|
3
3
|
export { parseDatabaseCommand } from './parsing.js';
|
|
4
4
|
import { queryDatabase } from './query.js';
|
|
5
|
+
export { DB_PRIMER, extractDatabaseCommand } from './primer.js';
|
|
5
6
|
function errorMessage(error) {
|
|
6
7
|
return error instanceof Error ? error.message : String(error);
|
|
7
8
|
}
|
|
@@ -51,33 +52,3 @@ export function runDatabaseCommand(input) {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
|
-
// Primer injected into an ACP agent so it understands the `db` grammar and can
|
|
55
|
-
// drive an autonomous tool loop: it emits a command, the host runs it, and the
|
|
56
|
-
// output is fed back until the agent answers without a command.
|
|
57
|
-
export const DB_PRIMER = [
|
|
58
|
-
'This host CLI can manage SQLite databases via `db` commands. Syntax:',
|
|
59
|
-
' db sqlite create <name> # create an empty database',
|
|
60
|
-
' db sqlite delete <name> # delete a database',
|
|
61
|
-
' db sqlite query <name> <sql> # run SQL against a database',
|
|
62
|
-
' db sqlite list # list databases',
|
|
63
|
-
'Database names use letters, numbers, "-" and "_" only; the engine is always "sqlite".',
|
|
64
|
-
'To inspect or change a database, end your reply with exactly one `db` command on its',
|
|
65
|
-
'own final line (no code fence, nothing after it). The host runs it automatically and',
|
|
66
|
-
'returns the output to you, so you can issue further commands. When the task is done,',
|
|
67
|
-
'reply with the final answer and NO trailing `db` command.',
|
|
68
|
-
'Be concise: do not explain what you are doing. Only output `db` commands and the final answer.',
|
|
69
|
-
].join('\n');
|
|
70
|
-
/**
|
|
71
|
-
* Pull a proposed `db ...` command out of an agent reply, if present. Scans
|
|
72
|
-
* bottom-up (the primer asks for the command on the last line) and tolerates a
|
|
73
|
-
* surrounding code fence or a leading `$ `/`> ` prompt marker.
|
|
74
|
-
*/
|
|
75
|
-
export function extractDatabaseCommand(text) {
|
|
76
|
-
const lines = text.split('\n');
|
|
77
|
-
for (let index = lines.length - 1; index >= 0; index--) {
|
|
78
|
-
const line = lines[index].replace(/^[\s`$>]+/, '').replace(/`+\s*$/, '').trim();
|
|
79
|
-
if (/^db\s+sqlite\s+(create|delete|query|list)\b/i.test(line))
|
|
80
|
-
return line;
|
|
81
|
-
}
|
|
82
|
-
// not a db command
|
|
83
|
-
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Primer injected into an ACP agent so it understands the `db` grammar and can
|
|
2
|
+
// drive an autonomous tool loop: it emits a command, the host runs it, and the
|
|
3
|
+
// output is fed back until the agent answers without a command.
|
|
4
|
+
export const DB_PRIMER = [
|
|
5
|
+
'This host CLI can manage SQLite databases via `db` commands. Syntax:',
|
|
6
|
+
' db sqlite create <name> # create an empty database',
|
|
7
|
+
' db sqlite delete <name> # delete a database',
|
|
8
|
+
' db sqlite query <name> <sql> # run SQL against a database',
|
|
9
|
+
' db sqlite list # list databases',
|
|
10
|
+
'Database names use letters, numbers, "-" and "_" only; the engine is always "sqlite".',
|
|
11
|
+
'To inspect or change a database, end your reply with exactly one `db` command on its',
|
|
12
|
+
'own final line (no code fence, nothing after it). The host runs it automatically and',
|
|
13
|
+
'returns the output to you, so you can issue further commands. When the task is done,',
|
|
14
|
+
'reply with the final answer and NO trailing `db` command.',
|
|
15
|
+
'Be concise: do not explain what you are doing. Only output `db` commands and the final answer.',
|
|
16
|
+
].join('\n');
|
|
17
|
+
/**
|
|
18
|
+
* Pull a proposed `db ...` command out of an agent reply, if present. Scans
|
|
19
|
+
* bottom-up (the primer asks for the command on the last line) and tolerates a
|
|
20
|
+
* surrounding code fence or a leading `$ `/`> ` prompt marker.
|
|
21
|
+
*/
|
|
22
|
+
export function extractDatabaseCommand(text) {
|
|
23
|
+
const lines = text.split('\n');
|
|
24
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
25
|
+
const line = lines[index].replace(/^[\s`$>]+/, '').replace(/`+\s*$/, '').trim();
|
|
26
|
+
if (/^db\s+sqlite\s+(create|delete|query|list)\b/i.test(line))
|
|
27
|
+
return line;
|
|
28
|
+
}
|
|
29
|
+
// not a db command
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Parses the argument tail of a `files [left|right] [path]` / `files in <label> [on <side>]`
|
|
2
|
+
// command. Consumes leading `in <label>` / `on <left|right>` clauses (either order, each at most
|
|
3
|
+
// once), then falls back to the bare `left`/`right` keyword if neither clause was used. Whatever's
|
|
4
|
+
// left over is the path target.
|
|
5
|
+
export function parseFileTreeArgs(rest) {
|
|
6
|
+
let cursor = rest;
|
|
7
|
+
let inLabel;
|
|
8
|
+
let dock = null;
|
|
9
|
+
for (;;) {
|
|
10
|
+
if (inLabel === undefined) {
|
|
11
|
+
const inMatch = /^in\s+(\S+)\b\s*/i.exec(cursor);
|
|
12
|
+
if (inMatch) {
|
|
13
|
+
inLabel = inMatch[1];
|
|
14
|
+
cursor = cursor.slice(inMatch[0].length);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (dock === null) {
|
|
19
|
+
const onMatch = /^on\s+(left|right)\b\s*/i.exec(cursor);
|
|
20
|
+
if (onMatch) {
|
|
21
|
+
dock = onMatch[1].toLowerCase();
|
|
22
|
+
cursor = cursor.slice(onMatch[0].length);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
if (inLabel === undefined && dock === null) {
|
|
29
|
+
const keyword = /^(left|right)\b\s*/i.exec(cursor);
|
|
30
|
+
if (keyword) {
|
|
31
|
+
dock = keyword[1].toLowerCase();
|
|
32
|
+
cursor = cursor.slice(keyword[0].length);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { inLabel, dock, target: cursor.trim() };
|
|
36
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { changedPaths, currentBranch } from '../git-status.js';
|
|
2
|
+
// Recompute one tab's git-changed set and current branch off the event loop, then re-render with
|
|
3
|
+
// them via `rebuild` (decision 6's second `dirty` emit). Coalesced: if a refresh is already in
|
|
4
|
+
// flight for this tab, the request only sets a stale bit and exactly one follow-up runs when the
|
|
5
|
+
// current one resolves — no overlapping git processes. The captured `root` guards against a
|
|
6
|
+
// mid-flight `reroot` or tab close: a result whose tab is gone or whose root has changed is
|
|
7
|
+
// discarded, never written.
|
|
8
|
+
export function refreshGit(states, label, rebuild) {
|
|
9
|
+
const state = states.get(label);
|
|
10
|
+
if (!state)
|
|
11
|
+
return;
|
|
12
|
+
if (state.gitRefreshing) {
|
|
13
|
+
state.gitRefreshStale = true;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
state.gitRefreshing = true;
|
|
17
|
+
const root = state.root;
|
|
18
|
+
void Promise.all([changedPaths(root), currentBranch(root)]).then(([changed, branch]) => {
|
|
19
|
+
const current = states.get(label);
|
|
20
|
+
if (!current)
|
|
21
|
+
return;
|
|
22
|
+
if (current.root === root) {
|
|
23
|
+
current.changed = changed;
|
|
24
|
+
current.branch = branch;
|
|
25
|
+
rebuild(label);
|
|
26
|
+
}
|
|
27
|
+
current.gitRefreshing = false;
|
|
28
|
+
if (current.gitRefreshStale) {
|
|
29
|
+
current.gitRefreshStale = false;
|
|
30
|
+
refreshGit(states, label, rebuild);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
// VS Code's `files.exclude` defaults. Other dotfiles are shown.
|
|
4
|
+
const EXCLUDES = new Set(['.git', '.svn', '.hg', '.DS_Store', 'Thumbs.db']);
|
|
5
|
+
// One directory's sorted, filtered entries: directories first, then files, `localeCompare`
|
|
6
|
+
// case-insensitive within each group. A symlink (file or directory) reports as a file — never
|
|
7
|
+
// expandable — which is the cheap way to stay cycle-proof. An unreadable directory (permission
|
|
8
|
+
// denied, deleted mid-read) yields [].
|
|
9
|
+
export function readDirSorted(absDir) {
|
|
10
|
+
let dirents;
|
|
11
|
+
try {
|
|
12
|
+
dirents = readdirSync(absDir, { withFileTypes: true });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const entries = dirents
|
|
18
|
+
.filter((d) => !EXCLUDES.has(d.name))
|
|
19
|
+
.map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
20
|
+
return entries.toSorted((a, b) => {
|
|
21
|
+
if (a.dir !== b.dir)
|
|
22
|
+
return a.dir ? -1 : 1;
|
|
23
|
+
return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// A depth-first, pre-flattened list of the currently *visible* rows: the root's direct children,
|
|
27
|
+
// plus — for each directory whose path is in `expanded` — that directory's children too, and so
|
|
28
|
+
// on. Children are read from disk only for expanded directories. An `expanded` entry for a path
|
|
29
|
+
// that no longer exists (or isn't found under its parent) is simply never reached, so it's
|
|
30
|
+
// naturally skipped rather than needing special-case pruning here.
|
|
31
|
+
export function buildRows(root, expanded) {
|
|
32
|
+
const rows = [];
|
|
33
|
+
const walk = (absDir, relDir, depth) => {
|
|
34
|
+
for (const entry of readDirSorted(absDir)) {
|
|
35
|
+
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
36
|
+
const isExpanded = entry.dir && expanded.has(relPath);
|
|
37
|
+
rows.push({
|
|
38
|
+
path: relPath, name: entry.name, depth, dir: entry.dir,
|
|
39
|
+
...(entry.dir && { expanded: isExpanded }),
|
|
40
|
+
});
|
|
41
|
+
if (isExpanded)
|
|
42
|
+
walk(path.join(absDir, entry.name), relPath, depth + 1);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
walk(root, '', 0);
|
|
46
|
+
if (path.dirname(root) !== root) {
|
|
47
|
+
rows.unshift({ path: '..', name: '..', depth: 0, dir: true });
|
|
48
|
+
}
|
|
49
|
+
return rows;
|
|
50
|
+
}
|
|
51
|
+
// Return `rows` with a `changed` flag set on every row git considers changed: a file row when its
|
|
52
|
+
// own `path` is in `changed`; a directory row when any changed path is nested beneath it (a prefix
|
|
53
|
+
// check, `changedPath` starts with `${row.path}/`). Propagation is purely this flat-set prefix
|
|
54
|
+
// scan — no directory is re-read, so a collapsed directory still colors when something deep inside
|
|
55
|
+
// it changed. Rows with no match are returned as-is; an empty `changed` set marks nothing.
|
|
56
|
+
export function markChanged(rows, changed) {
|
|
57
|
+
if (changed.size === 0)
|
|
58
|
+
return rows;
|
|
59
|
+
return rows.map((row) => {
|
|
60
|
+
const isChanged = row.dir
|
|
61
|
+
? [...changed].some((p) => p.startsWith(`${row.path}/`))
|
|
62
|
+
: changed.has(row.path);
|
|
63
|
+
return isChanged ? { ...row, changed: true } : row;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// The containing directory of a tree-relative path — the empty string for a root-level entry,
|
|
67
|
+
// matching the root-as-empty-string convention `buildRows` already uses.
|
|
68
|
+
export function parentPath(relPath) {
|
|
69
|
+
const idx = relPath.lastIndexOf('/');
|
|
70
|
+
return idx === -1 ? '' : relPath.slice(0, idx);
|
|
71
|
+
}
|
|
72
|
+
// True if `candidate` is `base` itself, or is nested inside it — the check that blocks dropping a
|
|
73
|
+
// dragged item onto itself or one of its own descendants (moving a directory into its own child).
|
|
74
|
+
export function isSameOrDescendantPath(candidate, base) {
|
|
75
|
+
return candidate === base || candidate.startsWith(`${base}/`);
|
|
76
|
+
}
|
|
77
|
+
// True if the directory at `absDestDir` already has a child named `name` — used to detect a
|
|
78
|
+
// same-name conflict at the drop target. Re-reads the destination directory fresh from disk
|
|
79
|
+
// rather than trusting the client's row list, which can be briefly stale.
|
|
80
|
+
export function hasNameConflict(absDestDir, name) {
|
|
81
|
+
return readDirSorted(absDestDir).some((e) => e.name === name);
|
|
82
|
+
}
|