@sabaiway/agent-workflow-kit 1.14.0 → 1.15.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/CHANGELOG.md +38 -0
- package/README.md +2 -1
- package/SKILL.md +25 -5
- package/capability.json +1 -1
- package/package.json +1 -1
- package/tools/uninstall.mjs +58 -10
- package/tools/uninstall.test.mjs +29 -0
- package/tools/velocity-profile.mjs +571 -0
- package/tools/velocity-profile.test.mjs +496 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
|
|
|
4
4
|
is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
|
|
5
5
|
every `migrations/<version>-<slug>.md` newer than it, in semver order.
|
|
6
6
|
|
|
7
|
+
## 1.15.0 — Velocity-profile onboarding (kit)
|
|
8
|
+
|
|
9
|
+
An opt-in **`/agent-workflow-kit velocity`** mode seeds a fixed, audited **read-only** Claude Code
|
|
10
|
+
allowlist into `.claude/settings.json` so an agent stops idling on approval prompts for routine
|
|
11
|
+
read-only commands while the maintainer is away. It never allowlists `commit`/`push`/`publish`, so a
|
|
12
|
+
direct invocation still ASKs — the only caveat is the trust-posture residual (below), closed by a
|
|
13
|
+
deferred hook.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **`tools/velocity-profile.mjs`** — the pure core (a frozen 18-entry `UNIVERSAL_READONLY_ALLOWLIST`,
|
|
17
|
+
the `screenAllowlistEntry` read-only screen, the read-only `discoverGateCandidates` gate advisor, and
|
|
18
|
+
the `validateProfile` drift guard) **plus** the programmatic settings writer + CLI
|
|
19
|
+
(`[--dry-run | --apply] [--accept-edits] [--cwd <dir>]`). Strict **preflight-then-mutate**:
|
|
20
|
+
merge-don't-clobber, opt-in `acceptEdits`; refuses an unsafe `permissions.defaultMode`
|
|
21
|
+
(`bypassPermissions` / any non-`{default,acceptEdits,plan}` mode present in **either** settings file),
|
|
22
|
+
a symlinked `.claude`, malformed settings JSON, or a non-current deployment stamp on `--apply`. Writes
|
|
23
|
+
**only** `.claude/settings.json`, never `settings.local.json`.
|
|
24
|
+
- A **`### Mode: velocity`** section + a `## Modes` dispatch entry + a one-line opt-in bootstrap offer
|
|
25
|
+
in `SKILL.md`.
|
|
26
|
+
- The guarded **`uninstall`** now also reports `permissions.defaultMode`/`permissions.allow` in
|
|
27
|
+
`.claude/settings.json` **non-committally** (REPORT_ONLY, never auto-removed — the writer stores no
|
|
28
|
+
ownership marker).
|
|
29
|
+
|
|
30
|
+
### Honesty
|
|
31
|
+
- This is the family's **first programmatic `.claude/settings.json` writer** — a new writer subsystem
|
|
32
|
+
with its own tests and teardown reporting, **not** merely an extension of the attribution prose seam.
|
|
33
|
+
- The audited core is **read-only by intent — verified, not assumed** (no mutating command, no inline
|
|
34
|
+
code execution): build-time probes proved `git grep` (`--open-files-in-pager`) and `sort`
|
|
35
|
+
(`--compress-program`) give inline code execution; both were dropped (the core is 18, not 20).
|
|
36
|
+
`git diff`/`log`/`show` are kept with a documented bounded-write (`--output`) residual.
|
|
37
|
+
- A seeded read-only allow entry is a **trust posture, not a sandbox**: Claude Code's settings-level
|
|
38
|
+
allow rules do not inspect output redirection (`cmd > file`) nor command substitution (`cmd $(…)`),
|
|
39
|
+
so that residual is surfaced honestly in the consent copy, bounded by `acceptEdits` staying opt-in,
|
|
40
|
+
and **fully closed only by a deferred PreToolUse hook** (a recorded follow-up). `commit`/`push`/
|
|
41
|
+
`publish` are never added as allow rules.
|
|
42
|
+
|
|
43
|
+
Lineage head stays **1.3.0** (no `docs/ai` structural change; no migration). See AD-021.
|
|
44
|
+
|
|
7
45
|
## 1.14.0 — Activity procedures: recipe-aware, configurable playbooks
|
|
8
46
|
|
|
9
47
|
A new read-only **`/agent-workflow-kit procedures <activity>`** advisor turns a bare command like
|
package/README.md
CHANGED
|
@@ -223,7 +223,8 @@ command is printed).
|
|
|
223
223
|
| `/agent-workflow-kit status` | any time | **read-only** view of the whole family: which members (kit / memory / engine / the two bridges) are installed and at what version, and — with a project — what's deployed (`docs/ai`, the version stamps, and whether the AI files are git-ignored for hidden mode). Never writes, never commits, never runs a subscription CLI. |
|
|
224
224
|
| `/agent-workflow-kit recipes` | any time | **read-only** orchestration advisor: presents four named recipes for composing the bridges into plan → execute → review — **Solo / Reviewed / Council / Delegated** — plans + recommends one for your environment (degrading with a stated reason when a backend isn't ready), and offers the choice. The orchestrator runs it via the bridge skills and **always commits**; the kit never executes a recipe, never runs a subscription CLI, never commits. |
|
|
225
225
|
| `/agent-workflow-kit procedures <activity>` | any time | **read-only** activity-procedures advisor: prints a named activity's ordered steps (`plan-authoring` / `plan-execution`) read **live** from the engine, plus the **resolved recipe per slot** from your hand-edited `docs/ai/orchestration.json` + backend readiness (default Reviewed when a backend is ready, Council on request, slot-aware incl. Delegated). `--override <slot>=<recipe>` adjusts one slot per run. Composes with `recipes`; never writes, never commits, never runs a subscription CLI. |
|
|
226
|
-
| `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md`
|
|
226
|
+
| `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md` (prints the exact `rm` to run by hand) or your `.claude/settings.json` (prints an **edit** — remove the attribution key, review any velocity `permissions.*` — never an `rm`). Always `--dry-run` first; preflight-then-mutate; never commits. |
|
|
227
|
+
| `/agent-workflow-kit velocity` | Claude Code · opt-in | **onboarding velocity profile** — seeds a fixed, audited **read-only** allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts while you're away; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. Writes **only** `.claude/settings.json` — **never** allowlists commit/push/publish, never writes `settings.local.json`, never commits. A seeded entry is a **trust posture, not a sandbox** (a redirection/command-substitution residual remains, closed by a deferred hook); a direct commit/push/publish still asks. `--dry-run` first. |
|
|
227
228
|
|
|
228
229
|
It **never auto-commits** and **never overwrites** an existing `AGENTS.md` without asking.
|
|
229
230
|
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: agent-workflow-kit
|
|
|
3
3
|
description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow".
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
|
-
version: '1.
|
|
6
|
+
version: '1.15.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
|
@@ -105,7 +105,8 @@ Pick the mode from the user's invocation. Auto-detect an existing `docs/ai/` to
|
|
|
105
105
|
- **`/agent-workflow-kit status`** — read-only view of the **whole family**: which members (kit / memory / engine / the two bridges) are installed, at what version, and — in a project — what is deployed (`docs/ai`, the version stamps, the hidden-mode fence). Never writes, never commits, never runs a subscription CLI.
|
|
106
106
|
- **`/agent-workflow-kit recipes`** — read-only **orchestration advisor**: present the four recipes (Solo / Reviewed / Council / Delegated) over the bridges' role vocabulary, plan + recommend one for the current environment, and offer the choice. **The orchestrator executes the chosen recipe via the bridge skills and always commits** — the kit only surfaces/selects/plans it; it never executes a recipe, never runs a subscription CLI, never commits.
|
|
107
107
|
- **`/agent-workflow-kit procedures <activity>`** — read-only **activity-procedures advisor**: print the ordered steps of a named activity (`plan-authoring` / `plan-execution`) read **live** from the installed engine (`references/procedures.md`), and the **resolved effective recipe per slot** from the per-project `docs/ai/orchestration.json` (hand-edited, strict JSON) + backend readiness (default = Reviewed when a backend is ready, Council on request, slot-aware incl. Delegated; graceful default vs loud override degradation). A per-run `--override <slot>=<recipe>` overrides one slot. Composes with `recipes` (which stays read-only); never writes, never commits, never runs a subscription CLI.
|
|
108
|
-
- **`/agent-workflow-kit uninstall`** — the **guarded teardown** companion to `init`/`setup`. Removes what they placed — installed skill dirs + the bridge wrappers — and, in a project, reverses the hidden-mode fence + the marker pre-commit hook. **Never deletes user-authored content
|
|
108
|
+
- **`/agent-workflow-kit uninstall`** — the **guarded teardown** companion to `init`/`setup`. Removes what they placed — installed skill dirs + the bridge wrappers — and, in a project, reverses the hidden-mode fence + the marker pre-commit hook. **Never deletes user-authored content**: it prints the exact `rm` for `docs/ai` / `AGENTS.md` and an **edit** for `.claude/settings.json` (the `includeCoAuthoredBy` key + any velocity `permissions.*`), for you to run by hand. `--dry-run` first, always; preflight-then-mutate; never commits.
|
|
109
|
+
- **`/agent-workflow-kit velocity`** — opt-in onboarding **velocity profile**: seed a fixed, audited **read-only** Claude Code allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. **Writes only `.claude/settings.json`, never allowlists commit/push/publish, never writes `settings.local.json`, never commits.** `--dry-run` first.
|
|
109
110
|
|
|
110
111
|
### Version status & the two axes — surface this on every invocation
|
|
111
112
|
|
|
@@ -185,7 +186,7 @@ readiness, then **append an actionable recipe recommendation** from the recipe p
|
|
|
185
186
|
independent axes: a packaging-only release bumps the package but leaves the lineage head until a
|
|
186
187
|
migration actually changes the deployed `docs/ai` structure. A stamp greater than the head →
|
|
187
188
|
STOP (never downgrade).
|
|
188
|
-
11. **Report & ask.** Show `tree docs/ai/`, 2–3 lines on what was filled with real data vs left as TODO, then print the **one-line backend-status line** (the shared contract above — same detector, format, invariants, and detector-unavailable skip-with-reason). Then **ask before committing** — never auto-commit.
|
|
189
|
+
11. **Report & ask.** Show `tree docs/ai/`, 2–3 lines on what was filled with real data vs left as TODO, then print the **one-line backend-status line** (the shared contract above — same detector, format, invariants, and detector-unavailable skip-with-reason). Then **ask before committing** — never auto-commit. Finally, offer the optional, opt-in **velocity profile** in one line — `/agent-workflow-kit velocity` seeds a read-only allowlist so routine read-only commands stop prompting (*Mode: velocity*); never run it without a yes.
|
|
189
190
|
|
|
190
191
|
Fill strategy:
|
|
191
192
|
|
|
@@ -317,10 +318,29 @@ It classifies every surface into four classes and acts accordingly:
|
|
|
317
318
|
|
|
318
319
|
- **remove** (safe) — an installed skill dir that is **provably ours** (valid manifest, `name`+`kind` match). A dir present but **not provably ours** (`foreign`/`stub`/`invalid`/unreadable) → **STOP**: left untouched **and reported**, while the teardown still removes the members that ARE ours (a not-ours surface is never clobbered, and never blocks removing the rest — the per-item `setup` posture). **Preflight-then-mutate:** if a mutable surface **changed since the dry-run** (a skill no longer ours, a wrapper turned foreign, a hook that lost our marker, a malformed fence), the run **aborts with zero changes**.
|
|
319
320
|
- **reverse** (managed-marker) — a bridge **wrapper symlink that points at our source** (a foreign/non-symlink one → STOP); the hidden-mode **managed fence** (via the existing `--unhide` path — only the fenced lines); a **pre-commit hook carrying our marker** (an unmarked / user hook → left + reported).
|
|
320
|
-
- **KEEP — never deleted** (report-only) — `docs/ai`, `AGENTS.md`, `CLAUDE.md`, `docs/plans`, and
|
|
321
|
+
- **KEEP — never deleted** (report-only) — `docs/ai`, `AGENTS.md`, `CLAUDE.md`, `docs/plans`, and `.claude/settings.json` (the `includeCoAuthoredBy` edit **and** any `permissions.defaultMode`/`permissions.allow` the velocity profile may have seeded). The tool **prints the exact `rm` / `git rm --cached`** for the docs/entry files and an **edit** instruction for `settings.json` (never an `rm` — it may hold your own settings); the **user** runs them. Surface this in plain language; never delete on their behalf.
|
|
321
322
|
|
|
322
323
|
**Shared globals:** removing `agent-workflow-memory` / `agent-workflow-engine` / a bridge removes a **global** skill that another project on the machine may use — say so before applying. **Windows:** the wrappers are POSIX; the skill-dir + project arms still work, the wrapper arm reports *use WSL*.
|
|
323
324
|
|
|
325
|
+
### Mode: velocity
|
|
326
|
+
|
|
327
|
+
The opt-in onboarding **velocity profile** — it seeds a fixed, audited **read-only** Claude Code allowlist into `.claude/settings.json` so an agent stops idling on approval prompts for routine read-only commands while the maintainer is away. It is the family's **first programmatic `.claude/settings.json` writer** (attribution stayed an agent-driven prose merge). **In-agent, opt-in, writes only `.claude/settings.json`**, on one hard rule: **it never allowlists `commit`/`push`/`publish`** — so a direct commit/push/publish still ASKs; the only caveat is the trust-posture residual (below), closed by the deferred hook.
|
|
328
|
+
|
|
329
|
+
**Version-status routing (like the other writer modes):** read `docs/ai/.workflow-version` first — not-deployed → bootstrap; stamp < `1.3.0` → `upgrade`; stamp > head / unparseable → STOP. The tool enforces this in code too (`--apply` STOPs unless the stamp is the lineage head).
|
|
330
|
+
|
|
331
|
+
Run `node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs [--dry-run | --apply] [--accept-edits] [--cwd <dir>]`:
|
|
332
|
+
|
|
333
|
+
1. **`--dry-run` first, always** (the default — changes nothing). It prints: the fixed read-only core it would add; a **read-only advisory** that lists your `package.json` `scripts` as **unaudited candidates you may add BY HAND** (inspect each first) to `.claude/settings.json` / `settings.local.json` — the tool **never** writes them and flags obviously-mutating names as "do not add"; any **pre-existing non-read-only `Bash(...)` entries** to consider removing by hand; and the honest residual notice (below). It STOPs (zero writes) on a symlinked `.claude` / non-regular `settings.json`, malformed settings JSON, or an unsafe `permissions.defaultMode` — `bypassPermissions` or anything outside `default`/`acceptEdits`/`plan`, present in **either** `settings.json` or `settings.local.json`.
|
|
334
|
+
2. **Ask the `acceptEdits` opt-in** via **`AskUserQuestion` where supported**, the safe option FIRST:
|
|
335
|
+
- **"Keep per-edit approval prompts (recommended)"** — seed only the read-only allowlist; file edits still prompt.
|
|
336
|
+
- **"Auto-accept file edits (`defaultMode: acceptEdits`)"** — present the honest FULL posture: it auto-applies Edit/Write AND auto-runs `mkdir`/`touch`/`mv`/`cp` in the working dir, is paired with the read-only allowlist, and — stated plainly — a settings-level allow rule is a **trust posture, not a sandbox**: a read-only entry can still write a file via output redirection, and (Claude Code's allow rules do not inspect command substitution) could in principle run another command via `cmd $(…)`. velocity **never adds `commit`/`push`/`publish` as allow rules** — so a direct `git push` still ASKs — but that same redirection/substitution residual means they are not *fully* closed until the deferred PreToolUse hook (family backlog). Note also that a `defaultMode` in `settings.local.json` would override this project-level write (local > project), since velocity writes only `.claude/settings.json`.
|
|
337
|
+
3. **Only on an explicit yes**, re-run with `--apply` (add `--accept-edits` only if they chose the second option). It merges-don't-clobber (preserves `includeCoAuthoredBy`, every key, and existing allow entries) and writes **only** `.claude/settings.json`.
|
|
338
|
+
4. **Surface delegation-readiness, read-only.** If they want a step run Delegated, point them at the hand-edited `docs/ai/orchestration.json` (*Mode: procedures*); the tool never writes it.
|
|
339
|
+
|
|
340
|
+
**Invariants:** creates `.claude/` if absent and writes **only** `.claude/settings.json` (no other file); **never** allowlists commit/push/publish; **never** writes `settings.local.json`; never commits; opt-in `acceptEdits`, never silent.
|
|
341
|
+
|
|
342
|
+
**Exit codes:** `0` done / dry-run; `1` a precondition STOP (stamp not current, unsafe mode, malformed settings, symlinked `.claude` / non-regular target); `2` bad arguments.
|
|
343
|
+
|
|
324
344
|
---
|
|
325
345
|
|
|
326
346
|
## Gotchas
|
|
@@ -337,7 +357,7 @@ The non-obvious traps — scan these before bootstrapping or upgrading. Each is
|
|
|
337
357
|
- **Conversational language never translates artifacts.** It governs *dialogue only*. Code, identifiers, paths, commands, log output, abbreviations, and every deployed `docs/ai/` / `AGENTS.md` file stay in their source language. See [Communication contract](references/contracts.md#communication-contract).
|
|
338
358
|
- **Never auto-commit.** Report quality-gate results and wait for explicit approval — in both modes.
|
|
339
359
|
- **Never leak kit internals to the user.** No ADR ids, tool / function / operation names (`reconcile`, `inject`, `ensureSlot`), marker / slot / fragment / anchor terminology, or verbatim tool stderr in anything the user reads. Translate every tool outcome into plain language a third-party user — who has never read this `SKILL.md` — can understand and act on (e.g. the cap-refusal report in *Mode: upgrade* step 3).
|
|
340
|
-
- **Uninstall never deletes user-authored content, and dry-runs first.** `/agent-workflow-kit uninstall` removes only what is **provably ours** (a managed skill dir / wrapper symlink / fenced block / marker hook) and **prints — never runs** the `rm` / `git rm --cached` for `docs/ai
|
|
360
|
+
- **Uninstall never deletes user-authored content, and dry-runs first.** `/agent-workflow-kit uninstall` removes only what is **provably ours** (a managed skill dir / wrapper symlink / fenced block / marker hook) and **prints — never runs** the `rm` / `git rm --cached` for `docs/ai` and the entry-point docs, and an **edit** instruction (not an `rm`) for `.claude/settings.json`. Always run `--dry-run` first, show the plan, get consent, then `--yes`. A skill dir or symlink that is not provably ours is a STOP, never a clobber (the `setup` posture, inverted). Removing a shared global (memory/engine/a bridge) may affect another project — say so.
|
|
341
361
|
|
|
342
362
|
---
|
|
343
363
|
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
package/tools/uninstall.mjs
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
// family-registry (the SKILL axis) + surveyProject (the DEPLOY axis) and classifies every surface it
|
|
6
6
|
// could touch into one of four classes, then mutates ONLY after preflighting all of them (AD-011:
|
|
7
7
|
// a conflict on a later item leaves the filesystem untouched). The hard rule: it NEVER deletes
|
|
8
|
-
// user-authored content
|
|
9
|
-
//
|
|
8
|
+
// user-authored content — it PRINTS the exact remove commands for docs/ai + the entry-point docs, and
|
|
9
|
+
// an EDIT instruction for .claude/settings.json (never an rm), for the user to run by hand (the
|
|
10
|
+
// AD-014 tracked-file posture, generalized to teardown).
|
|
10
11
|
//
|
|
11
12
|
// safe-remove — kit-placed + provably ours: a family skill dir (valid manifest, name+kind match).
|
|
12
13
|
// managed-marker — recognized by an OWNED marker: a wrapper symlink that points at our source, the
|
|
@@ -193,8 +194,15 @@ export const buildPlan = ({ family, project = null, projectDir = null, member =
|
|
|
193
194
|
return null;
|
|
194
195
|
}
|
|
195
196
|
})();
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
const settingsSeams = detectSettingsSeams(settings);
|
|
198
|
+
if (settingsSeams.attribution || settingsSeams.permissions) {
|
|
199
|
+
items.push({
|
|
200
|
+
surface: 'settings',
|
|
201
|
+
path: settingsPath,
|
|
202
|
+
class: REPORT_ONLY,
|
|
203
|
+
reason: settingsSeamReason(settingsSeams),
|
|
204
|
+
hand: settingsSeamHand(settingsSeams, settingsPath),
|
|
205
|
+
});
|
|
198
206
|
}
|
|
199
207
|
|
|
200
208
|
for (const rel of REPORT_PATHS) {
|
|
@@ -306,12 +314,51 @@ const CLASS_LABEL = { [SAFE_REMOVE]: 'remove', [MANAGED_MARKER]: 'reverse', [REP
|
|
|
306
314
|
// or shell metacharacters can't misbehave when the user pastes it (codex #4).
|
|
307
315
|
const shq = (p) => `'${String(p).replace(/'/g, "'\\''")}'`;
|
|
308
316
|
|
|
309
|
-
//
|
|
310
|
-
//
|
|
317
|
+
// Detect the two settings.json seams the family may have written: the attribution edit
|
|
318
|
+
// (`includeCoAuthoredBy`) and the velocity profile (`permissions.defaultMode` / `permissions.allow`).
|
|
319
|
+
// Parse when possible (accurate); on malformed JSON fall back to a substring probe for the attribution
|
|
320
|
+
// key so it is still surfaced (no silent miss). The velocity writer stores NO ownership marker, so the
|
|
321
|
+
// permissions seam is reported NON-COMMITTALLY — never a false ownership claim, never auto-removed.
|
|
322
|
+
const detectSettingsSeams = (settings) => {
|
|
323
|
+
if (settings == null) return { attribution: false, permissions: false };
|
|
324
|
+
const parsed = (() => { try { return JSON.parse(settings); } catch { return null; } })();
|
|
325
|
+
if (parsed == null || typeof parsed !== 'object') {
|
|
326
|
+
// Malformed / JSONC settings.json (comments, trailing commas) — probe both seams by substring so
|
|
327
|
+
// neither is silently missed (over-reporting REPORT_ONLY is safe; it is never auto-removed).
|
|
328
|
+
return {
|
|
329
|
+
attribution: settings.includes('includeCoAuthoredBy'),
|
|
330
|
+
permissions: settings.includes('"permissions"') && (settings.includes('"defaultMode"') || settings.includes('"allow"')),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const perms = parsed.permissions;
|
|
334
|
+
const permissions = perms != null && typeof perms === 'object'
|
|
335
|
+
&& (Object.prototype.hasOwnProperty.call(perms, 'defaultMode') || Object.prototype.hasOwnProperty.call(perms, 'allow'));
|
|
336
|
+
return { attribution: Object.prototype.hasOwnProperty.call(parsed, 'includeCoAuthoredBy'), permissions };
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const ATTRIBUTION_REASON = 'we set "includeCoAuthoredBy": false here — review/remove that key by hand (the file may hold your own settings)';
|
|
340
|
+
const PERMISSIONS_REASON = 'a "permissions.defaultMode" and/or "permissions.allow" key is present in this file — if the velocity profile seeded them, review/remove by hand (no ownership marker is stored); otherwise leave them';
|
|
341
|
+
|
|
342
|
+
const settingsSeamReason = (seams) =>
|
|
343
|
+
seams.attribution && seams.permissions ? `${ATTRIBUTION_REASON}. Also: ${PERMISSIONS_REASON}`
|
|
344
|
+
: seams.permissions ? PERMISSIONS_REASON
|
|
345
|
+
: ATTRIBUTION_REASON;
|
|
346
|
+
|
|
347
|
+
const settingsSeamHand = (seams, p) =>
|
|
348
|
+
seams.attribution && seams.permissions
|
|
349
|
+
? `edit ${shq(p)} → remove the "includeCoAuthoredBy" entry and review "permissions.defaultMode"/"permissions.allow" (if the velocity profile seeded them) — keep the rest of your settings`
|
|
350
|
+
: seams.permissions
|
|
351
|
+
? `edit ${shq(p)} → if the velocity profile seeded "permissions.defaultMode"/"permissions.allow", review/remove them by hand (keep the rest of your settings)`
|
|
352
|
+
: `edit ${shq(p)} → remove the "includeCoAuthoredBy" entry (keep the rest of your settings)`;
|
|
353
|
+
|
|
354
|
+
// The "do this by hand" line for a report-only surface. A settings.json item carries its own `hand`
|
|
355
|
+
// guidance (an EDIT, never an `rm` — deleting it would lose the user's own settings, codex #4);
|
|
356
|
+
// everything else is a quoted rm. The fallback preserves the attribution wording for a bare item.
|
|
311
357
|
const handGuidance = (item) =>
|
|
312
|
-
item.
|
|
358
|
+
item.hand ??
|
|
359
|
+
(item.surface === 'settings'
|
|
313
360
|
? `edit ${shq(item.path)} → remove the "includeCoAuthoredBy" entry (keep the rest of your settings)`
|
|
314
|
-
: `rm -rf ${shq(item.path)} # if it was committed: git rm -r --cached ${shq(item.path)}
|
|
361
|
+
: `rm -rf ${shq(item.path)} # if it was committed: git rm -r --cached ${shq(item.path)}`);
|
|
315
362
|
|
|
316
363
|
export const formatPlan = (plan) => {
|
|
317
364
|
const lines = ['agent-workflow uninstall — planned actions (nothing is changed without --yes)', ''];
|
|
@@ -379,8 +426,9 @@ Usage:
|
|
|
379
426
|
--yes apply the auto-removable set (skill dirs + wrappers + fence + marker hook)
|
|
380
427
|
--help this help
|
|
381
428
|
|
|
382
|
-
It NEVER deletes user-authored content
|
|
383
|
-
|
|
429
|
+
It NEVER deletes user-authored content — docs/ai and the entry-point docs are reported with the exact
|
|
430
|
+
rm commands for you to run, and .claude/settings.json with an EDIT instruction (remove the attribution
|
|
431
|
+
key; review any velocity permissions.*), never an rm. A skill dir not provably ours is left untouched.`;
|
|
384
432
|
|
|
385
433
|
const main = (argv) => {
|
|
386
434
|
const args = parseArgs(argv);
|
package/tools/uninstall.test.mjs
CHANGED
|
@@ -163,6 +163,35 @@ describe('buildPlan — project deploy axis', () => {
|
|
|
163
163
|
assert.equal(settings.class, REPORT_ONLY);
|
|
164
164
|
});
|
|
165
165
|
|
|
166
|
+
it('reports velocity permissions.* in settings.json NON-COMMITTALLY (never auto-removed)', () => {
|
|
167
|
+
const fs = projectFs({ files: { [join(dir, '.claude/settings.json')]: JSON.stringify({ permissions: { defaultMode: 'acceptEdits', allow: ['Bash(git status:*)'] } }) } });
|
|
168
|
+
const plan = buildPlan({ family: [], project, projectDir: dir }, fs);
|
|
169
|
+
const settings = plan.items.find((i) => i.surface === 'settings');
|
|
170
|
+
assert.equal(settings.class, REPORT_ONLY);
|
|
171
|
+
assert.match(settings.reason, /velocity profile seeded them/);
|
|
172
|
+
assert.doesNotMatch(settings.reason, /includeCoAuthoredBy/);
|
|
173
|
+
const out = formatPlan(plan);
|
|
174
|
+
assert.match(out, /permissions\.defaultMode/);
|
|
175
|
+
assert.ok(!/rm -rf .*settings\.json/.test(out), 'settings.json is never rm-ed');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('reports BOTH the attribution edit and velocity permissions when both are present', () => {
|
|
179
|
+
const fs = projectFs({ files: { [join(dir, '.claude/settings.json')]: JSON.stringify({ includeCoAuthoredBy: false, permissions: { allow: ['Bash(cat:*)'] } }) } });
|
|
180
|
+
const settings = buildPlan({ family: [], project, projectDir: dir }, fs).items.find((i) => i.surface === 'settings');
|
|
181
|
+
assert.equal(settings.class, REPORT_ONLY);
|
|
182
|
+
assert.match(settings.reason, /includeCoAuthoredBy/);
|
|
183
|
+
assert.match(settings.reason, /permissions\.(defaultMode|allow)/);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('falls back to a substring probe on malformed settings JSON (no silent miss of either seam)', () => {
|
|
187
|
+
const broken = '{ "includeCoAuthoredBy": false, "permissions": { "allow": ["Bash(ls:*)"] },, }'; // double comma → JSON.parse throws
|
|
188
|
+
const fs = projectFs({ files: { [join(dir, '.claude/settings.json')]: broken } });
|
|
189
|
+
const settings = buildPlan({ family: [], project, projectDir: dir }, fs).items.find((i) => i.surface === 'settings');
|
|
190
|
+
assert.equal(settings.class, REPORT_ONLY);
|
|
191
|
+
assert.match(settings.reason, /includeCoAuthoredBy/);
|
|
192
|
+
assert.match(settings.reason, /permissions/);
|
|
193
|
+
});
|
|
194
|
+
|
|
166
195
|
it('reports docs/ai, AGENTS.md, CLAUDE.md, docs/plans as never-deleted', () => {
|
|
167
196
|
const fs = projectFs({ files: { [join(dir, 'AGENTS.md')]: 'x', [join(dir, 'CLAUDE.md')]: 'x' } });
|
|
168
197
|
const docs = buildPlan({ family: [], project, projectDir: dir }, fs).items.filter((i) => i.surface === 'docs');
|