@jaybeeuu/agent-cortex 1.42.0 → 2.0.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/.agents/skills/style-versioning/SKILL.md +78 -8
- package/.github/workflows/ci.yml +51 -38
- package/AGENTS.md +32 -17
- package/CHANGELOG.md +182 -0
- package/README.md +167 -126
- package/agents/README.md +26 -27
- package/agents/ralph/agent.md +7 -10
- package/agents/ralph/claude/polling.md +1 -1
- package/bin/agent-cortex.mjs +46 -44
- package/bin/installers/claude.mjs +2 -2
- package/bin/installers/pi.mjs +885 -237
- package/docs/claude-hooks.md +5 -4
- package/docs/commit-signing.md +231 -0
- package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +10 -1
- package/docs/ideas/extension-manifests.md +5 -0
- package/docs/research/disable-model-invocation.md +82 -0
- package/docs/research/skill-design-research.md +2 -2
- package/docs/skills/skill-anatomy.md +5 -2
- package/docs/technical-direction/harness-config.md +30 -14
- package/docs/technical-direction/migration-execution-labels.md +17 -9
- package/docs/user-preferences.md +2 -2
- package/extensions/agent-modes/README.md +3 -3
- package/extensions/agent-modes/discover.test.ts +19 -0
- package/extensions/agent-modes/discover.ts +31 -9
- package/extensions/session-start/README.md +38 -0
- package/extensions/session-start/index.test.ts +122 -0
- package/extensions/session-start/index.ts +27 -0
- package/extensions/session-start/package.json +19 -0
- package/extensions/session-start/tsconfig.json +12 -0
- package/extensions/skill-stats/pnpm-lock.yaml +329 -0
- package/extensions/subagent/README.md +3 -3
- package/extensions/subagent/agents.test.ts +176 -0
- package/extensions/subagent/agents.ts +101 -11
- package/extensions/subagent/index.ts +2 -1
- package/extensions/subagent/package.json +1 -1
- package/lib/cli.mjs +40 -16
- package/package.json +8 -9
- package/pi/settings.json +1 -7
- package/pnpm-workspace.yaml +6 -0
- package/scripts/check-changeset.mjs +42 -0
- package/scripts/lib/compose-agent.mjs +10 -10
- package/scripts/lib/release-gates.mjs +61 -0
- package/scripts/lib/verify-worker-tree.mjs +107 -0
- package/scripts/resolve-publish-command.mjs +17 -0
- package/skills/planning/create-task/scripts/pnpm-lock.yaml +342 -0
- package/skills/productivity/validate-agent-dir/scripts/pnpm-lock.yaml +329 -0
- package/skills/workflow/git-workflow/SKILL.md +35 -1
- package/skills/workflow/ralph/REFERENCE.md +20 -4
- package/skills/workflow/ralph/SKILL.md +9 -6
- package/skills/workflow/run-pipeline-stage/SKILL.md +11 -4
- package/skills/workflow/run-pipeline-stage/playbooks/code.md +2 -0
- package/skills/workflow/run-pipeline-stage/playbooks/document.md +3 -0
- package/skills/workflow/run-pipeline-stage/playbooks/fix.md +2 -0
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +11 -0
- package/skills/workflow/run-pipeline-stage/scripts/pnpm-lock.yaml +342 -0
- package/test/cli.test.mjs +37 -39
- package/test/helpers/fake-pi.mjs +132 -0
- package/test/pi-installer.test.mjs +345 -286
- package/test/pi-register.test.mjs +342 -0
- package/test/pi-sweep.test.mjs +619 -0
- package/test/release-gates.test.mjs +190 -0
- package/test/verify-worker-tree.test.mjs +120 -0
- package/token-map.README.md +16 -18
- package/token-map.json +29 -33
- package/agents/plan/copilot/frontmatter.json +0 -6
- package/agents/plan.agent.md +0 -59
- package/agents/ralph/copilot/frontmatter.json +0 -6
- package/agents/ralph/copilot/polling.md +0 -62
- package/agents/ralph-plan/copilot/frontmatter.json +0 -6
- package/agents/ralph-plan.agent.md +0 -126
- package/agents/ralph.agent.md +0 -331
- package/agents/strategy/copilot/frontmatter.json +0 -6
- package/agents/strategy.agent.md +0 -112
- package/bin/installers/copilot.mjs +0 -124
- package/extensions/session-start.ts +0 -44
- package/hooks.json +0 -10
- package/lib/pi-packages.mjs +0 -169
- package/plugin.json +0 -35
- package/scripts/build-copilot-agents.mjs +0 -30
- package/scripts/install-instructions.sh +0 -82
- package/scripts/sync-plugin-version.sh +0 -17
- package/test/copilot-installer.test.mjs +0 -308
- package/test/pi-packages.test.mjs +0 -295
package/docs/claude-hooks.md
CHANGED
|
@@ -14,7 +14,7 @@ UI surface. Each decision below is grounded in the Claude Code hooks reference.
|
|
|
14
14
|
|
|
15
15
|
| pi extension | Claude port | Decision |
|
|
16
16
|
|---|---|---|
|
|
17
|
-
| `session-start
|
|
17
|
+
| `session-start/` | `SessionStart` hook commands | **Port** (extended) |
|
|
18
18
|
| `auto-name.ts` | — | **Reject** — built-in |
|
|
19
19
|
| `agent-guardrails/` | — | **Reject** — pi-only (no system-prompt hook) |
|
|
20
20
|
| `notify/` | `Notification` hook + `scripts/notify.mjs` | **Port** |
|
|
@@ -22,10 +22,11 @@ UI surface. Each decision below is grounded in the Claude Code hooks reference.
|
|
|
22
22
|
| `subagent/` | — | **Reject** — native |
|
|
23
23
|
| `agent-modes/` | — | **Reject** — no equivalent |
|
|
24
24
|
|
|
25
|
-
## `session-start
|
|
25
|
+
## `session-start/` → SessionStart (port)
|
|
26
26
|
|
|
27
|
-
pi injects
|
|
28
|
-
|
|
27
|
+
pi injects the project context files (AGENTS.md, `docs/user-preferences.md`)
|
|
28
|
+
into every session; it lists no skills, since pi's system prompt already names
|
|
29
|
+
every skill with its `SKILL.md` path. Claude Code already loads
|
|
29
30
|
`AGENTS.md` natively and the existing SessionStart hook nudge ("Load AGENTS.md
|
|
30
31
|
and active beads context…") plus the style-skill policy (PR #57) cover the
|
|
31
32
|
context side. This bead closes the remaining gap: a skill-policy command listing
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Commit signing
|
|
2
|
+
|
|
3
|
+
GitHub has refused merges here with **"Merging is blocked — Commits must have verified
|
|
4
|
+
signatures"**. The instinctive reading — "agents don't sign, so of course" — is wrong:
|
|
5
|
+
agent and human commits on this repo are GPG-signed and verified. The unsigned commits
|
|
6
|
+
are the ones `github-actions[bot]` writes, and only because `GITHUB_TOKEN` cannot hold a
|
|
7
|
+
signing key. This note records what is actually signed, what the API says about
|
|
8
|
+
enforcement, and the release policy that follows.
|
|
9
|
+
|
|
10
|
+
## What is signed, and what is not
|
|
11
|
+
|
|
12
|
+
The agent environment signs automatically. Global git config has `commit.gpgsign=true`
|
|
13
|
+
and the secret key `DF657A88675E4AF8F7D65192A8F2E11DE960BE54` (Josh Bickley-Wallace,
|
|
14
|
+
the `noreply` address) is present, so every commit an agent makes is signed without any
|
|
15
|
+
per-commit step. Verified against the API:
|
|
16
|
+
|
|
17
|
+
| Commit | Author | `verification` |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `63f3bef` (PR #181 head) | jaybeeuu | `verified=true reason=valid` |
|
|
20
|
+
| `7f99e4c` (PR #182 head) | jaybeeuu | `verified=true reason=valid` |
|
|
21
|
+
| `9443f55` (PR #168 head) | github-actions[bot] | `verified=false reason=unsigned` |
|
|
22
|
+
|
|
23
|
+
`changesets/action` commits as `github-actions[bot]` using `secrets.GITHUB_TOKEN`, and
|
|
24
|
+
GitHub does not sign commits made with a token. That is a property of the token, not a
|
|
25
|
+
missing configuration step — no amount of workflow YAML changes it.
|
|
26
|
+
|
|
27
|
+
Landed history looks uniformly `valid` for a different reason: the commit the merge creates
|
|
28
|
+
is signed by GitHub. `dd347471b1`, the commit that landed PR #161 ("chore: version
|
|
29
|
+
packages", authored by `github-actions[bot]`), has `committer=GitHub` and
|
|
30
|
+
`verification.verified=true reason=valid`. The bot's unsigned branch commit never reached
|
|
31
|
+
`main`; the squash commit GitHub created did, and GitHub signed it.
|
|
32
|
+
|
|
33
|
+
That is what this repository's history proves, and it is all it proves. It does **not**
|
|
34
|
+
prove that a squash merge escapes an *enforced* signature rule.
|
|
35
|
+
|
|
36
|
+
## Squash does not bypass an enforced signature rule
|
|
37
|
+
|
|
38
|
+
The landing commit being GitHub-signed does not exempt the pull request from the check:
|
|
39
|
+
a ruleset that requires signatures inspects the head-branch commits too. GitHub's ruleset
|
|
40
|
+
reference, ["Require signed commits"](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#require-signed-commits)
|
|
41
|
+
(quoted verbatim, checked 2026-10-27):
|
|
42
|
+
|
|
43
|
+
> When GitHub evaluates whether a pull request can be merged, it creates a test merge commit
|
|
44
|
+
> whose parents are the latest commit on the base branch and the pull request's head commit.
|
|
45
|
+
> GitHub checks the commits introduced by this test merge, including commits from the head
|
|
46
|
+
> branch. As a result, unsigned commits on the head branch can block a squash merge, even
|
|
47
|
+
> though GitHub would sign the final squash commit. This restriction can also apply to the
|
|
48
|
+
> author of the pull request.
|
|
49
|
+
|
|
50
|
+
and the remedy it documents:
|
|
51
|
+
|
|
52
|
+
> To merge a blocked pull request, rewrite and sign the unsigned commits on the head branch,
|
|
53
|
+
> or ask someone with permission to bypass the applicable protections to merge the pull
|
|
54
|
+
> request.
|
|
55
|
+
|
|
56
|
+
So "squash it and let GitHub re-sign" is not a way *around* the check — it is only the reason
|
|
57
|
+
landed history reads `valid` when no signature rule is in force. If a signature rule is
|
|
58
|
+
enforced, the remedy is a signed commit, not a different merge method.
|
|
59
|
+
|
|
60
|
+
## What the ruleset API shows — and what it cannot
|
|
61
|
+
|
|
62
|
+
No `required_signatures` rule is visible to the tokens available here, at any level.
|
|
63
|
+
Read-only probes:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
$ gh api repos/jaybeeuu/agent-cortex/rules/branches/main --jq '[.[].type]'
|
|
67
|
+
["deletion","non_fast_forward","required_linear_history",
|
|
68
|
+
"required_status_checks","pull_request"]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every effective rule on `main` comes from `ruleset_id 22101709` with
|
|
72
|
+
`ruleset_source_type: Repository`. That is evidence rather than silence, because the endpoint
|
|
73
|
+
is not repository-scoped by construction: it is documented to return rules "regardless of the
|
|
74
|
+
level at which they are configured (e.g. repository or organization)", and its own response
|
|
75
|
+
schema includes a `ruleset_source_type: "Organization"` example ([REST reference, "Get rules for a branch"](https://docs.github.com/en/rest/repos/rules#get-rules-for-a-branch), checked
|
|
76
|
+
2026-10-27). There is no `required_signatures` rule in it, and its `pull_request` parameters
|
|
77
|
+
allow `["merge","squash","rebase"]`. The other read paths agree:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
$ gh api repos/jaybeeuu/agent-cortex/branches/main/protection
|
|
81
|
+
{"message":"Branch not protected", ... "status":"404"}
|
|
82
|
+
|
|
83
|
+
$ gh api orgs/jaybeeuu/rulesets
|
|
84
|
+
{"message":"Not Found", ... "status":"404"}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Two explanations remain. The branch-rule read above weighs heavily against the first, without
|
|
88
|
+
formally eliminating it:
|
|
89
|
+
|
|
90
|
+
1. **An organization-level ruleset enforces signatures.** This is the weaker of the two now:
|
|
91
|
+
the applied-rule read covers every level and shows no organization-sourced rule, so this
|
|
92
|
+
survives only on an undocumented possibility that the endpoint filters by caller
|
|
93
|
+
visibility. Org ruleset *definitions* do separately require organization administration
|
|
94
|
+
read, which the current token does not hold (scopes: `gist, read:org, repo, workflow`), so
|
|
95
|
+
a `404` on `orgs/jaybeeuu/rulesets` is inconclusive — it may mean "none exist" or "you may
|
|
96
|
+
not see them". Note that the ruleset's `bypass_actors` is empty and
|
|
97
|
+
`current_user_can_bypass` is `"never"`, so repository admin does **not** bypass it.
|
|
98
|
+
2. **The UI message was stale** — shown while a signature rule was active, before ruleset
|
|
99
|
+
`22101709` was last updated (2026-09-13).
|
|
100
|
+
|
|
101
|
+
Closing this out needs a merge attempt (a mutation) or org-admin access: see *The enforcing
|
|
102
|
+
rule: still open (AC1)* and the human follow-ups below.
|
|
103
|
+
|
|
104
|
+
One adjacent rule is worth knowing, though it cannot fire here. Ruleset `22101709` sets
|
|
105
|
+
`require_extra_approval_for_unattributed_changes: true`, which adds one approval when a pull
|
|
106
|
+
request is an unattributed Copilot pull request — but GitHub documents that the setting "has
|
|
107
|
+
no effect if the ruleset requires zero approvals" ([available rules for rulesets](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#additional-approval-for-unattributed-copilot-pull-requests),
|
|
108
|
+
checked 2026-10-27), and this ruleset sets `required_approving_review_count: 0`. A blocked
|
|
109
|
+
merge box here is the missing `statusCheckRollup` on a Version Packages PR or a missing
|
|
110
|
+
code-owner review, not this setting.
|
|
111
|
+
|
|
112
|
+
## Policy: land bot-authored PRs by squash merge
|
|
113
|
+
|
|
114
|
+
Adopted: **Option A — no signing key in CI.** So long as no signature rule is enforced, no
|
|
115
|
+
unsigned commit is required to land a release: the merge produces a commit GitHub signs, so
|
|
116
|
+
the only requirement is the merge method.
|
|
117
|
+
|
|
118
|
+
Squash-merge Version Packages PRs (`gh pr merge <n> --squash`). GitHub rewrites the bot's
|
|
119
|
+
unsigned branch commit into a single commit that it signs as committer, which is exactly
|
|
120
|
+
what `dd347471b1` demonstrates. Squash is the only path that lands a GitHub-signed commit:
|
|
121
|
+
`allow_merge_commit` is `false` in the repository merge settings, so a true merge commit is
|
|
122
|
+
impossible, and `required_linear_history` on `main` means a pull request "must use a squash
|
|
123
|
+
merge or a rebase merge". The ruleset's `allowed_merge_methods`
|
|
124
|
+
(`["merge","squash","rebase"]`) leaves both open — its `merge` entry is already dead because
|
|
125
|
+
the repository disables merge commits. Of those two, only squash produces a commit GitHub
|
|
126
|
+
creates and signs: rebase-and-merge is documented to add the head-branch commits "without
|
|
127
|
+
commit signature verification", because the commit it creates is modified and GitHub "can't
|
|
128
|
+
therefore sign it as a generic system user" ([about merge methods](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github),
|
|
129
|
+
checked 2026-10-27). For a `github-actions[bot]` head commit that means an unsigned commit on
|
|
130
|
+
`main`, so rebase is not an acceptable path here even though the settings permit it.
|
|
131
|
+
|
|
132
|
+
Note the limit of this remedy: squash re-signs the landing commit, it does not bypass an
|
|
133
|
+
enforced signature rule (see above). It works here because no such rule is currently
|
|
134
|
+
enforced against this repository.
|
|
135
|
+
|
|
136
|
+
Consequence: the release automation stays fully automated and no signing key enters CI —
|
|
137
|
+
provided no signature rule is enforced. See the contingency below.
|
|
138
|
+
|
|
139
|
+
### Contingency: the enforcing rule turns out to be real at organization level
|
|
140
|
+
|
|
141
|
+
The policy above holds only while a squash merge is permitted. If the merge probe or an org
|
|
142
|
+
admin confirms an organization-level `required_signatures` rule, Option A's premise fails:
|
|
143
|
+
GitHub would then refuse the squash merge as well, because it validates the pull request's
|
|
144
|
+
head commits through the test merge. In that case:
|
|
145
|
+
|
|
146
|
+
1. Do not keep re-attempting the squash merge, and do not treat a bypass as the workaround —
|
|
147
|
+
a bypass is a human permission decision, not a release path.
|
|
148
|
+
2. The release path must produce signed commits, which means Option B (import a key into the
|
|
149
|
+
release job) or having a key-holding actor sign the release commit. That change must land
|
|
150
|
+
with the access audit and rotation procedure Option B requires.
|
|
151
|
+
3. Until then, releasing is a human step performed with bypass permission, recorded as a
|
|
152
|
+
HITL note on the bead.
|
|
153
|
+
|
|
154
|
+
The applied-rule read returns no organization-sourced rule, but org ruleset definitions stay
|
|
155
|
+
unreadable from a repository-scoped token, so re-check the read before assuming the absence
|
|
156
|
+
persists.
|
|
157
|
+
|
|
158
|
+
### Option B, rejected as the default
|
|
159
|
+
|
|
160
|
+
Importing a GPG key into the release job (`GPG_PRIVATE_KEY` + `GPG_PASSPHRASE` secrets and
|
|
161
|
+
a `git commit --amend -S` step) would make the *branch* commit signed too. It is rejected as
|
|
162
|
+
the default because, while no signature rule is enforced, it buys nothing the squash merge
|
|
163
|
+
does not already give — the branch commit is discarded on merge — while placing a
|
|
164
|
+
long-lived private key in repository secrets. That trade-off inverts if a signature rule is
|
|
165
|
+
confirmed enforced: Option B then becomes necessary (see the contingency above). Either way,
|
|
166
|
+
if it is revisited the same change must carry an access audit (who and what can read the
|
|
167
|
+
secret) and a rotation procedure (key expiry, re-issue, revocation). Neither exists today,
|
|
168
|
+
and adding the key without them is not acceptable.
|
|
169
|
+
|
|
170
|
+
## When the signature check fires
|
|
171
|
+
|
|
172
|
+
| Situation | Remedy |
|
|
173
|
+
|---|---|
|
|
174
|
+
| Bot-authored PR blocked on signatures, rule **confirmed** enforced | A squash merge will not help — GitHub checks head-branch commits through its test merge. Rewrite the branch so its commits are signed (`git rebase --exec 'git commit --amend -S --no-edit'` or an amend plus force-push), then merge; otherwise a person with bypass permission merges it. |
|
|
175
|
+
| Bot-authored PR blocked, `rules/branches/main` lists no `required_signatures` rule | The block is most likely not signatures. Read the merge box first: missing required status checks (an empty `statusCheckRollup` on a Version Packages PR is the common case) or a missing code-owner review fully explain `mergeStateStatus=BLOCKED`. |
|
|
176
|
+
| An agent commit is reported unsigned | Check `git config --get commit.gpgsign` and `gpg --list-secret-keys`; re-sign with `git commit --amend -S --no-edit` and force-push the branch. |
|
|
177
|
+
| The block persists with no visible cause | Read `gh api repos/<owner>/<repo>/rules/branches/<branch>`. That endpoint covers every level, so no `required_signatures` entry means no active signature rule applies as far as this read can see — if the block still looks like signatures, have an org admin confirm the org rulesets rather than guessing. |
|
|
178
|
+
|
|
179
|
+
## The enforcing rule: still open (AC1)
|
|
180
|
+
|
|
181
|
+
The parent bead's first acceptance criterion — "Enforcing rule identified, or the UI message
|
|
182
|
+
shown to be stale — with the merge API's error text quoted either way" — is **open**. No
|
|
183
|
+
merge API error text exists yet, because obtaining it requires a merge attempt, which is a
|
|
184
|
+
mutation and therefore a human step. Read-only evidence narrows the question sharply but
|
|
185
|
+
does not close it:
|
|
186
|
+
|
|
187
|
+
1. **No repository-level signature rule was blocking on 2026-10-13.** PR #161's head
|
|
188
|
+
`fe7ef84` is `verification.verified=false reason=unsigned`, and the PR was squash-merged
|
|
189
|
+
at `2026-10-13T21:46:06Z` — after ruleset `22101709`'s last update
|
|
190
|
+
(`2026-09-13T21:11:23.985+01:00`, i.e. `2026-09-13T20:11:23Z`). An unsigned head landed
|
|
191
|
+
after the ruleset's last edit, so neither that ruleset nor anything older in it enforced
|
|
192
|
+
signatures then.
|
|
193
|
+
2. **PR #168's current block needs no signature rule to explain it.**
|
|
194
|
+
`gh pr view 168 --json state,mergeable,mergeStateStatus,statusCheckRollup,reviews` returns
|
|
195
|
+
`state=OPEN`, `mergeable=MERGEABLE`, `mergeStateStatus=BLOCKED`,
|
|
196
|
+
`statusCheckRollup=[]`, `reviews=[]`. All five required checks (`Test`, `Lint`,
|
|
197
|
+
`Claude plugin materialiser check`, `Changeset check`, `Setup`) are unreported, and
|
|
198
|
+
`require_code_owner_review` is on with no reviews. `required_status_checks` plus the
|
|
199
|
+
missing code-owner review fully account for `BLOCKED`.
|
|
200
|
+
3. **The applied-rule read comes back clean, including at organization level.**
|
|
201
|
+
`rules/branches/{branch}` is documented to return every active rule "regardless of the
|
|
202
|
+
level at which they are configured (e.g. repository or organization)", so it is the read
|
|
203
|
+
that *would* surface an organization-level rule. The live read returns five rules, all
|
|
204
|
+
`ruleset_source_type: Repository` from `ruleset_id 22101709`, and none of them is
|
|
205
|
+
`required_signatures`. On the documented contract that is positive evidence that no
|
|
206
|
+
organization-level signature rule is applied to `main` today, not an unreadable gap. The
|
|
207
|
+
honest residual is narrower: whether this endpoint filters by caller visibility is
|
|
208
|
+
undocumented, and org ruleset *definitions* stay unreadable with the current token —
|
|
209
|
+
`gh api orgs/jaybeeuu/rulesets` returns `{"message":"Not Found", ... "status":"404"}` and
|
|
210
|
+
`gh api orgs/jaybeeuu` adds `gh: This API operation needs the "admin:org" scope`. So "no
|
|
211
|
+
org rule is applied" is read; "no org ruleset exists" is not.
|
|
212
|
+
|
|
213
|
+
Status: **open, pending a human merge probe or org-admin inspection** (see below). The read
|
|
214
|
+
above narrows the question but cannot close AC1, which turns on the merge API's own error
|
|
215
|
+
text; the merge probe supplies that.
|
|
216
|
+
|
|
217
|
+
## Human-only follow-ups
|
|
218
|
+
|
|
219
|
+
These need repository or organization settings and cannot be done by an agent:
|
|
220
|
+
|
|
221
|
+
- **Merge probe — closes AC1.** Run `gh pr merge 168 --squash` (or
|
|
222
|
+
`PUT /repos/jaybeeuu/agent-cortex/pulls/168/merge`) and record the verbatim error text
|
|
223
|
+
here. A signature error confirms an invisible rule and triggers the contingency above; a
|
|
224
|
+
status-check error confirms the message was stale and closes the criterion as
|
|
225
|
+
"UI message stale".
|
|
226
|
+
- **Inspect org rulesets.** Open `https://github.com/organizations/jaybeeuu/settings/rules`
|
|
227
|
+
and confirm whether a ruleset targets `jaybeeuu/agent-cortex` with "Require signed
|
|
228
|
+
commits". Record the answer here.
|
|
229
|
+
- **Confirm the merge method.** If squash-merge is the documented path, consider removing
|
|
230
|
+
`rebase` from `allowed_merge_methods` so a rebase merge cannot add an unsigned bot
|
|
231
|
+
commit to `main`.
|
package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md
CHANGED
|
@@ -30,4 +30,13 @@ The author. Main-branch history becomes verifiably authentic — every commit at
|
|
|
30
30
|
Determine how the changesets commit gets signed in CI on this repo — cheapest prototype: scratch repo (or the version branch) exercising the signing path end-to-end without touching `main`'s rule, then evaluate the rule change on `main`.
|
|
31
31
|
|
|
32
32
|
## Notes
|
|
33
|
-
Recorded 2026-09-02. Backlog capture only — no active work planned; revisit after the install-manager epic (pi/claude/copilot) settles. Adjacent to `docs/ideas/unified-ci-pipeline.md` (CI/release restructure).
|
|
33
|
+
Recorded 2026-09-02. Backlog capture only — no active work planned; revisit after the install-manager epic (pi/claude/copilot) settles. Adjacent to `docs/ideas/unified-ci-pipeline.md` (CI/release restructure).
|
|
34
|
+
|
|
35
|
+
Updated 2026-10-28: `docs/commit-signing.md` records the investigation and the decided policy.
|
|
36
|
+
The applied-rule read shows no `required_signatures` rule on `main` at repository or
|
|
37
|
+
organization level (org ruleset *definitions* stay unreadable with a repository-scoped token),
|
|
38
|
+
and Version Packages PRs land by squash merge, whose landing commit GitHub signs — so releases
|
|
39
|
+
stay automated with no key in CI, and the CI signing key this idea points at is rejected as the
|
|
40
|
+
default. That rejection does not settle the idea: GitHub checks head-branch commits through its
|
|
41
|
+
test merge, so enabling enforcement would still require the signed CI commit, exactly as
|
|
42
|
+
`Next validation step` describes.
|
|
@@ -7,6 +7,11 @@ harness's native dependency fields plus committed settings templates. See the de
|
|
|
7
7
|
prune TUI was rejected (see "Prune rejected") and the proposed declared-vs-installed diff was
|
|
8
8
|
dropped — `agent-cortex install <harness>` reconciles instead.
|
|
9
9
|
|
|
10
|
+
The mechanism recorded under "What shipped" has since been removed (2026-10-24,
|
|
11
|
+
`agnt-ctx-7kru.1`): `pi.packages`, `lib/pi-packages.mjs` and `--no-provision` are gone, and the
|
|
12
|
+
required pi packages now travel inside the staged package at `~/.agent-cortex/pi` instead of
|
|
13
|
+
being provisioned into pi's user scope.
|
|
14
|
+
|
|
10
15
|
## Created
|
|
11
16
|
2026-08-25
|
|
12
17
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# `disable-model-invocation` across harnesses
|
|
2
|
+
|
|
3
|
+
Decision record for the frontmatter field this repo uses to keep rare or destructive
|
|
4
|
+
skills out of automatic model routing (bead `agnt-ctx-yf24`).
|
|
5
|
+
|
|
6
|
+
**Verdict:** rely on it for pi and Claude Code.
|
|
7
|
+
|
|
8
|
+
## What the field does
|
|
9
|
+
|
|
10
|
+
`disable-model-invocation: true` removes a skill from the list the model is offered.
|
|
11
|
+
The user can still invoke it explicitly. It does not summarise, rewrite, or
|
|
12
|
+
otherwise change how the body loads, which is what `docs/skills/skill-anatomy.md`
|
|
13
|
+
previously implied.
|
|
14
|
+
|
|
15
|
+
## Preservation and honouring
|
|
16
|
+
|
|
17
|
+
| Harness | Field survives materialisation | Loader honours it | Evidence |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| pi 1.4.0 | Yes — byte-identical copy | Yes — hidden from the prompt, `/skill:name` still works | `bin/installers/pi.mjs:182-204`, `scripts/lib/compose-agent.mjs:69-95`; `dist/core/skills.js:262` (parse) and `:279` (filter); `docs/skills.md:53` |
|
|
20
|
+
| Claude Code 2.1.294 | Yes — byte-identical copy | Yes — Claude cannot invoke it, `/skill-name` still works | `bin/installers/claude.mjs:160-201`, same token helper; [Claude Code skills docs](https://code.claude.com/docs/en/skills) ("Control who invokes a skill") |
|
|
21
|
+
|
|
22
|
+
Both materialisers copy `SKILL.md` as text and rewrite only `{{TOOL:...}}` /
|
|
23
|
+
`{{PATH:...}}` tokens, so frontmatter keys they do not know pass through untouched —
|
|
24
|
+
nothing re-serialises YAML. Verified by installing both harnesses into
|
|
25
|
+
`/var/tmp/dmi-check` and diffing `skills/productivity/teach/SKILL.md` (the repo's
|
|
26
|
+
only current user of the field): both copies are byte-identical to the source.
|
|
27
|
+
`claude plugin validate /var/tmp/dmi-check/claude/skills` passes, i.e. Claude Code's
|
|
28
|
+
own validator accepts the field rather than warning about it.
|
|
29
|
+
|
|
30
|
+
pi was probed against the materialised tree with its own loader: 32 skills loaded,
|
|
31
|
+
31 advertised, `teach` parsed with `disableModelInvocation: true` and absent from
|
|
32
|
+
`formatSkillsForPrompt` output.
|
|
33
|
+
|
|
34
|
+
## Per-harness detail
|
|
35
|
+
|
|
36
|
+
**pi.** Documented and implemented. `docs/skills.md:53`: "Set
|
|
37
|
+
`disable-model-invocation: true` in frontmatter when a skill should be available only
|
|
38
|
+
through its explicit command", with the frontmatter table listing it as "Hide the
|
|
39
|
+
skill from automatic model selection".
|
|
40
|
+
|
|
41
|
+
**Claude Code.** Documented, and the docs are explicit about the enforcement: "Claude
|
|
42
|
+
can't invoke the skill on its own … If Claude tries anyway, Claude Code blocks the
|
|
43
|
+
call and instructs it not to reproduce the deploy steps another way", and it "removes
|
|
44
|
+
the skill from Claude's context entirely".
|
|
45
|
+
|
|
46
|
+
Two Claude-specific caveats:
|
|
47
|
+
|
|
48
|
+
- The field is a Claude Code extension, not part of the
|
|
49
|
+
[Agent Skills spec](https://agentskills.io/specification), whose six fields
|
|
50
|
+
(`name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools`)
|
|
51
|
+
are otherwise exhaustive. Claude Code ignores unknown fields, but packing or
|
|
52
|
+
uploading a skill to claude.ai (or through the Skills API) "fails with a hard error
|
|
53
|
+
instead of ignoring the field". Our plugin path is unaffected; a skill we ever
|
|
54
|
+
publish to claude.ai must drop the field.
|
|
55
|
+
- `skillOverrides` does not apply to plugin skills, and our install is a plugin. The
|
|
56
|
+
frontmatter field itself still applies there — only that settings-based alternative
|
|
57
|
+
is unavailable.
|
|
58
|
+
|
|
59
|
+
## Options for "explicit invocation only", evaluated
|
|
60
|
+
|
|
61
|
+
| Option | Portable? | Enforced? | Verdict |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `disable-model-invocation: true` | pi + Claude Code yes | Yes | **Recommended** where suppressing auto-routing is the point and the explicit path is a human typing `/name` |
|
|
64
|
+
| Naming convention (e.g. `manual-*`) | Yes | No — no harness reads names | Rejected: a human signal we already get from the body |
|
|
65
|
+
| `metadata:` block | Yes (spec-legal) | No — pi and Claude read top-level keys only | Rejected: documentation that looks like enforcement |
|
|
66
|
+
| Keep such skills out of `skills/` | Yes | Yes, by not shipping the skill | Rejected: gives up the per-harness install entirely |
|
|
67
|
+
|
|
68
|
+
## Recommendation
|
|
69
|
+
|
|
70
|
+
Use the field when the danger is fuzzy automatic matching and the intended explicit
|
|
71
|
+
path is a human typing `/skill-name`: that is the case for a migration skill whose
|
|
72
|
+
`--apply` renames labels workspace-wide.
|
|
73
|
+
|
|
74
|
+
## Reproducing
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
node bin/agent-cortex.mjs install claude --output /var/tmp/dmi-check/claude
|
|
78
|
+
node bin/agent-cortex.mjs install pi --output /var/tmp/dmi-check/pi
|
|
79
|
+
diff skills/productivity/teach/SKILL.md /var/tmp/dmi-check/claude/skills/teach/SKILL.md
|
|
80
|
+
diff skills/productivity/teach/SKILL.md /var/tmp/dmi-check/pi/skills/productivity/teach/SKILL.md
|
|
81
|
+
claude plugin validate /var/tmp/dmi-check/claude/skills
|
|
82
|
+
```
|
|
@@ -35,7 +35,7 @@ The headline finding: **agent-cortex skills are well-structured but systematical
|
|
|
35
35
|
| Cross-skill references | Explicit inline calls: "run `/grill-with-docs`" | Implicit — no inline cross-refs | agent-cortex weaker |
|
|
36
36
|
| Shared vocabulary file | `CONTEXT.md` at repo root; skills told to read it | None | Missing in agent-cortex |
|
|
37
37
|
| Skill manifest | Flat JSON array of skill paths | Same | ✅ Aligned |
|
|
38
|
-
| `disable-model-invocation` | Used for pure prompt-injection skills |
|
|
38
|
+
| `disable-model-invocation` | Used for pure prompt-injection skills | Used by `teach`; semantics now documented in `docs/skills/skill-anatomy.md` | Field adopted; rationale corrected |
|
|
39
39
|
| Missing SKILL.md | 0 | 1 (`prd-to-epics`) | Bug in agent-cortex |
|
|
40
40
|
|
|
41
41
|
### 2.2 Tone and Voice
|
|
@@ -251,7 +251,7 @@ These are patterns present in `mattpocock/skills` that have no equivalent here a
|
|
|
251
251
|
|
|
252
252
|
2. **Philosophy sections in skills** — `tdd` doesn't just describe TDD, it argues *why* horizontal slicing is wrong. This bakes intent into the skill so the agent handles novel cases correctly (backed by CoT rationale evidence).
|
|
253
253
|
|
|
254
|
-
3. **`disable-model-invocation: true`** —
|
|
254
|
+
3. **`disable-model-invocation: true`** — hides a skill from automatic model routing so only an explicit `/skill-name` invocation reaches it. See `docs/research/disable-model-invocation.md` for per-harness behaviour and caveats.
|
|
255
255
|
|
|
256
256
|
4. **Composability by design** — skills reference each other by slash-command inline. This keeps each skill shorter and creates a network of reusable primitives rather than monolithic blobs.
|
|
257
257
|
|
|
@@ -11,7 +11,10 @@ Required fields:
|
|
|
11
11
|
|
|
12
12
|
Optional fields:
|
|
13
13
|
- `argument-hint`: input prompt shown to users at invocation.
|
|
14
|
-
- `disable-model-invocation`:
|
|
14
|
+
- `disable-model-invocation`: hides the skill from automatic model routing; explicit
|
|
15
|
+
`/skill-name` invocation still works. Set it when the danger is a fuzzy description
|
|
16
|
+
match, e.g. a skill whose steps are destructive. Preserved and honoured by pi and
|
|
17
|
+
Claude Code. See `docs/research/disable-model-invocation.md`.
|
|
15
18
|
|
|
16
19
|
## Description format rules
|
|
17
20
|
|
|
@@ -31,7 +34,7 @@ Optional fields:
|
|
|
31
34
|
|
|
32
35
|
## Tool references
|
|
33
36
|
|
|
34
|
-
Reference agent tools by `{{TOOL:name}}` token (canonical
|
|
37
|
+
Reference agent tools by `{{TOOL:name}}` token (canonical authoring name), never a
|
|
35
38
|
hardcoded harness-specific name such as `bash`, `task`, or `read_agent` — installers
|
|
36
39
|
substitute the per-harness name from `token-map.json` at install time. Add any new tool
|
|
37
40
|
to the map's `tools` section in the same change; unknown tokens are hard errors. See
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
# Technical Direction: Harness config and third-party extensions
|
|
2
2
|
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
**Partly superseded (2026-10-24) by the staged-package model** (`agnt-ctx-7kru`). Two premises
|
|
6
|
+
below no longer hold:
|
|
7
|
+
|
|
8
|
+
- **The pi half landed without bundling.** `agent-cortex install pi` stages a self-contained
|
|
9
|
+
package at `~/.agent-cortex/pi` and copies the required third-party packages — resolved
|
|
10
|
+
through Node's own resolution — into that tree's `node_modules/`, instead of declaring
|
|
11
|
+
`bundledDependencies` in the published tarball. pi loads a local directory and resolves no
|
|
12
|
+
npm packages itself, so a tarball's `node_modules/` reached nobody; bundling also forced a
|
|
13
|
+
repo-wide `node-linker=hoisted`, surrendering pnpm's isolated resolution for the workspace.
|
|
14
|
+
- **pi's own settings are no longer installer output.** The installer writes nothing into
|
|
15
|
+
`~/.pi/agent`; it registers the staged package by driving pi's own `pi install` /
|
|
16
|
+
`pi remove` commands, which own `settings.json` → `packages[]`. `pi/settings.json` and
|
|
17
|
+
`pi/keybindings.json` are reference templates. Registration's invariant is **one
|
|
18
|
+
agent-cortex source**: a source pointing at a raw agent-cortex checkout is removed first,
|
|
19
|
+
and one that survives `pi remove` fails the run rather than leave two sources configured
|
|
20
|
+
that both load.
|
|
21
|
+
|
|
22
|
+
`pi.packages` and `lib/pi-packages.mjs` are deleted, so the bespoke-pi-mechanism problem this
|
|
23
|
+
memo was written to settle is closed. The Claude analysis stands; Copilot CLI is no longer a
|
|
24
|
+
deploy target (`agnt-ctx-2rl`).
|
|
25
|
+
|
|
3
26
|
## Problem and target outcome
|
|
4
27
|
|
|
5
|
-
- **What we are solving.** agent-cortex ships agents and skills to
|
|
6
|
-
Code
|
|
7
|
-
|
|
28
|
+
- **What we are solving.** agent-cortex ships agents and skills to two harnesses (pi, Claude
|
|
29
|
+
Code) and needs a declared, reproducible set of *third-party* extensions those agents depend
|
|
30
|
+
on (pi packages / Claude plugins). Two bespoke mechanisms currently exist and
|
|
8
31
|
disagree: `main` declares packages in `package.json` → `pi.packages` and provisions them from
|
|
9
32
|
`lib/pi-packages.mjs`; the unmerged pi-harness epic (`agnt-ctx-dfq3`) declares them in
|
|
10
33
|
`pi.extensions.json` / `claude.extensions.json` and provisions them via `agent-cortex ext
|
|
@@ -29,9 +52,6 @@
|
|
|
29
52
|
- **Claude is idiomatic two ways.** Machine state is `~/.claude/settings.json` → `enabledPlugins`
|
|
30
53
|
+ `extraKnownMarketplaces`. A plugin declares other plugins in `.claude-plugin/plugin.json` →
|
|
31
54
|
`dependencies`, which Claude Code resolves and auto-installs.
|
|
32
|
-
- **Copilot has no dependency mechanism.** `copilot plugin install/list/uninstall` exists, but its
|
|
33
|
-
state is auto-managed `~/.copilot/config.json` (not committable) and `plugin.json` declares no
|
|
34
|
-
dependencies.
|
|
35
55
|
- **The epic already built two-thirds of the target.** Unmerged commit `afab2c6` (F3) materialises
|
|
36
56
|
`pi/settings.json` + `pi/keybindings.json` from committed templates with merge-keep-personal and
|
|
37
57
|
the CLI owning `packages`; `1ff212d` (F4) makes bundled pi extensions load via package
|
|
@@ -64,7 +84,7 @@
|
|
|
64
84
|
--json` are the exact machine-state comparison; drift self-heals on install.
|
|
65
85
|
- **Cons.** pi bundling shifts update cadence to agent-cortex releases and grows the tarball;
|
|
66
86
|
Claude cross-marketplace deps need an allowlist; the committed template also carries personal
|
|
67
|
-
settings
|
|
87
|
+
settings.
|
|
68
88
|
- **Risks.** Loading TS extensions from bundled `node_modules/`; maintaining the Claude
|
|
69
89
|
cross-marketplace allowlist.
|
|
70
90
|
|
|
@@ -106,8 +126,6 @@ the epic, and delete `pi.packages` + `lib/pi-packages.mjs` from `main`.
|
|
|
106
126
|
- **Committed personal settings.** `pi/settings.json` already carries models/theme; the template
|
|
107
127
|
model makes the repo own the whole pi settings surface. Accepted; F3's merge preserves personal
|
|
108
128
|
keys and only `packages` is CLI-owned.
|
|
109
|
-
- **Copilot gets no mechanism.** Documented exception — the README says `copilot plugin install`.
|
|
110
|
-
Accepted because Copilot has no dependency field and no committable config.
|
|
111
129
|
- **No dedicated diff command.** A read-only `status` is deferred until drift is shown to hurt.
|
|
112
130
|
|
|
113
131
|
## Validation plan
|
|
@@ -138,8 +156,6 @@ pi extension loads from `node_modules/` and an optional one is materialised from
|
|
|
138
156
|
speculative and should be dropped rather than maintained.
|
|
139
157
|
- Bundling `pi-web-access`'s native-ish dependency tree (unpdf, linkedom) proves brittle or
|
|
140
158
|
unacceptable on a fresh machine.
|
|
141
|
-
- Copilot gains a committable config or a plugin dependency field — revisit the documented
|
|
142
|
-
exception.
|
|
143
159
|
- Drift between the committed template and the machine becomes a recurring annoyance — add the
|
|
144
160
|
deferred read-only `status`.
|
|
145
161
|
|
|
@@ -153,9 +169,9 @@ pi extension loads from `node_modules/` and an optional one is materialised from
|
|
|
153
169
|
(auto-install, cross-marketplace allowlist, tag naming).
|
|
154
170
|
- Claude Code, *Settings* — https://code.claude.com/docs/en/settings (`enabledPlugins`,
|
|
155
171
|
`extraKnownMarketplaces`).
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
- Code evidence (as of 2026-09 — `pi.packages` and `lib/pi-packages.mjs` were removed in
|
|
173
|
+
`agnt-ctx-7kru.1`, see Status): `package.json` (`pi.packages`), `lib/pi-packages.mjs` (main's
|
|
174
|
+
provisioner);
|
|
159
175
|
`pi.extensions.json`, `claude.extensions.json`, `lib/extension-manifest.mjs` (epic F5, `1e0e970`);
|
|
160
176
|
`bin/installers/pi.mjs` settings materialisation (epic F3, `afab2c6`);
|
|
161
177
|
`docs/ideas/extension-manifests.md` (the originating idea).
|
|
@@ -13,14 +13,21 @@
|
|
|
13
13
|
|
|
14
14
|
## Current-state constraints
|
|
15
15
|
|
|
16
|
-
- bd
|
|
17
|
-
|
|
16
|
+
- bd is a globally installed CLI resolved from `PATH` (whatever single `bd` is on PATH —
|
|
17
|
+
Homebrew, npm, or the install script); `BD_PATH` is the override for machines where `bd` is
|
|
18
|
+
not on PATH. Skill scripts invoke `BD_PATH || 'bd'` and never hardcode an install path.
|
|
19
|
+
Label ops: `bd label list-all [--json]`, `bd list --label-pattern/regex [--json]`,
|
|
20
|
+
`bd label add/remove` (batch multiple ids).
|
|
21
|
+
- A `bd` whose install target has been removed (`~/.local/bin` orphans, dangling pnpm shims)
|
|
22
|
+
must not be pinned: it can be a schema behind the workspace DB and fail with a schema
|
|
23
|
+
version mismatch. Keep exactly one `bd` on PATH so `bd` and the DB version agree.
|
|
18
24
|
- `bd list` supports `--label-pattern`/`--json`; `bd count` does NOT support `--label-pattern`.
|
|
19
25
|
- `bd ready --label-pattern 'stage:*'` works; `bd ready -l execution:afk` verified.
|
|
20
|
-
- 3 script packages exist with
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`
|
|
26
|
+
- 3 script packages still exist with their own `package.json` (create-task,
|
|
27
|
+
validate-agent-dir, run-pipeline-stage). They ARE root workspace members (root glob
|
|
28
|
+
`skills/**/scripts`; all three are in the root `pnpm-lock.yaml`). Residual per-skill
|
|
29
|
+
`pnpm-lock.yaml` (all three) and `pnpm-workspace.yaml` (validate-agent-dir,
|
|
30
|
+
run-pipeline-stage) are stale artefacts of the pre-D2 layout.
|
|
24
31
|
- All 3 scripts are stdlib-only (`node:child_process`, `node:fs`, `node:path`, `node:url`).
|
|
25
32
|
- Extensions (`extensions/skill-stats`) have real peer deps on `@earendil-works/pi-coding-agent`
|
|
26
33
|
— they remain workspace members with their own package.json.
|
|
@@ -162,8 +169,9 @@
|
|
|
162
169
|
|
|
163
170
|
## References
|
|
164
171
|
|
|
165
|
-
- Code evidence: `bin/agent-cortex.mjs` + `lib/cli.mjs` (CLI surface),
|
|
166
|
-
package.json` (3
|
|
167
|
-
(`bd list`, `bd label`, `bd create`), epic
|
|
172
|
+
- Code evidence: `bin/agent-cortex.mjs` + `lib/cli.mjs` (CLI surface),
|
|
173
|
+
`skills/*/*/scripts/package.json` (3, root workspace members), root `package.json` +
|
|
174
|
+
`pnpm-workspace.yaml`, bd CLI help (`bd list`, `bd label`, `bd create`), epic
|
|
175
|
+
`agnt-ctx-s5xd` notes.
|
|
168
176
|
- Idea file: `docs/ideas/ctx-alias-version-and-update-commands.md` (ctx alias + version/update
|
|
169
177
|
surface that `migrate` joins).
|
package/docs/user-preferences.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# User Preferences
|
|
2
2
|
|
|
3
3
|
This file records personal development preferences for the author (**jaybeeuu**).
|
|
4
|
-
Both
|
|
5
|
-
|
|
4
|
+
Both pi and Claude Code agents should read this file for user-level context when
|
|
5
|
+
making tooling and workflow decisions.
|
|
6
6
|
|
|
7
7
|
## Package manager
|
|
8
8
|
|
|
@@ -39,9 +39,9 @@ contract:
|
|
|
39
39
|
from the tool set with a warning.
|
|
40
40
|
|
|
41
41
|
Flat `agents/*.agent.md` files are parsed as a fallback when an agent has no
|
|
42
|
-
composable directory —
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
composable directory — a staged pi package carries flat renders, and a hand-written
|
|
43
|
+
agent may be a single flat file. A composable directory always wins over its flat
|
|
44
|
+
counterpart for the same agent id.
|
|
45
45
|
|
|
46
46
|
## Development
|
|
47
47
|
|
|
@@ -244,6 +244,25 @@ describe("discoverAgents (flat fallback)", () => {
|
|
|
244
244
|
assert.deepEqual(strategy.tools, ["bash", "fetch_content", "read"]);
|
|
245
245
|
assert.match(strategy.prompt, /Flat body of strategy\./);
|
|
246
246
|
});
|
|
247
|
+
it("reads a space-separated scalar tools list instead of falling back to defaults", () => {
|
|
248
|
+
const fx = track(makeFixture());
|
|
249
|
+
writeFlatAgent(fx, "ralph", FLAT_FRONTMATTER("ralph", '"bash read grep task"'));
|
|
250
|
+
|
|
251
|
+
const agents = discoverAgents(fx.agentsDir, REAL_TOKEN_MAP);
|
|
252
|
+
|
|
253
|
+
// A collapsed scalar would land on DEFAULT_TOOLS, lifting the restriction
|
|
254
|
+
// rather than applying it — the tool names must survive the parse.
|
|
255
|
+
assert.deepEqual(agents[0].tools, ["bash", "grep", "read", "task"]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("reads a comma-separated scalar tools list", () => {
|
|
259
|
+
const fx = track(makeFixture());
|
|
260
|
+
writeFlatAgent(fx, "ralph", FLAT_FRONTMATTER("ralph", '"bash, view, task"'));
|
|
261
|
+
|
|
262
|
+
const agents = discoverAgents(fx.agentsDir, REAL_TOKEN_MAP);
|
|
263
|
+
|
|
264
|
+
assert.deepEqual(agents[0].tools, ["bash", "read", "task"]);
|
|
265
|
+
});
|
|
247
266
|
});
|
|
248
267
|
|
|
249
268
|
// ─── loadTokenMap ────────────────────────────────────────────────────────────
|