@passioncode-ai/passioncode 0.2.15 → 0.2.17

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/family.json +2 -2
  3. package/package.json +1 -1
  4. package/payload/.claude-plugin/marketplace.json +3 -3
  5. package/payload/manifest.json +12 -12
  6. package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  7. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
  8. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
  9. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -3
  10. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +8 -0
  11. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +9 -4
  12. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +7 -2
  13. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +1 -1
  14. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +4 -0
  15. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +3 -1
  16. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +7 -1
  17. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +33 -0
  18. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +12 -5
  19. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +16 -3
  20. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +32 -0
  21. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +18 -4
  22. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  23. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +2 -1
  24. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
  25. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +1 -1
  26. package/payload/plugins/observatory-log/.claude-plugin/plugin.json +1 -1
  27. package/payload/plugins/observatory-log/hooks/ask-why.py +141 -36
  28. package/payload/plugins/observatory-log/hooks/hooks.json +13 -1
  29. package/payload/plugins/observatory-log/hooks/prompt-why.py +180 -0
  30. package/payload/plugins/observatory-log/hooks/record-turn.sh +31 -15
  31. package/payload/plugins/observatory-log/hooks/session-start.sh +10 -4
  32. package/payload/plugins/observatory-log/skills/explaining-changes/SKILL.md +16 -5
  33. package/payload/plugins/observatory-log/skills/handling-secrets/SKILL.md +83 -13
  34. package/payload/plugins/observatory-log/skills/tracking-resources/SKILL.md +1 -1
  35. package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
@@ -88,7 +88,8 @@ if [ -f "$root/tools/maintain.py" ]; then
88
88
  else
89
89
  # The bridge for engines from before 0.17.0, which carry no updater of their own:
90
90
  # once a day, check; when a newer stable release exists and the person has not turned
91
- # automatic updates off (`updates.auto` false), install it with `full update --apply`.
91
+ # automatic updates off (the `auto-update` file says `off`, or `updates.auto` is false),
92
+ # install it with `full update --apply`.
92
93
  po=""
93
94
  [ -n "$venv" ] && [ -x "$venv/bin/project-observatory" ] && po="$venv/bin/project-observatory"
94
95
  [ -n "$po" ] || po="$(command -v project-observatory 2>/dev/null)"
@@ -96,10 +97,15 @@ else
96
97
  detach "$stamps/update-bridge.log" /bin/bash -c '
97
98
  po="$1"; py="$2"
98
99
  home="${OBSERVATORY_FULL_HOME:-${OBSERVATORY_HOME:-$HOME/.local/share/project-observatory-full}}"
99
- off="$("$py" -c "import json,sys
100
- try: d=json.load(open(sys.argv[1]))
100
+ # The switch (LC-16): the file `auto-update` in the home, where only `off` is off;
101
+ # without one, `updates.auto: false` in settings.json, as those releases wrote it.
102
+ off="$("$py" -c "import json,os,sys
103
+ f=os.path.join(sys.argv[1], \"auto-update\")
104
+ if os.path.isfile(f) and not os.path.islink(f):
105
+ print(1 if open(f, errors=\"replace\").read(64).strip().lower() == \"off\" else 0); sys.exit()
106
+ try: d=json.load(open(os.path.join(sys.argv[1], \"config\", \"settings.json\")))
101
107
  except Exception: d={}
102
- print(1 if (d.get(\"updates\") or {}).get(\"auto\") is False else 0)" "$home/config/settings.json" 2>/dev/null)"
108
+ print(1 if (d.get(\"updates\") or {}).get(\"auto\") is False else 0)" "$home" 2>/dev/null)"
103
109
  [ "$off" = "1" ] && { echo "$(date -u +%FT%TZ) automatic updates are off"; exit 0; }
104
110
  "$po" full update --check >/dev/null 2>&1; rc=$?
105
111
  echo "$(date -u +%FT%TZ) check exit $rc"
@@ -15,7 +15,7 @@ description: >-
15
15
  decision with different evidence.
16
16
  license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
17
17
  metadata:
18
- version: "0.18.0"
18
+ version: "0.26.0"
19
19
  compatibility: >-
20
20
  Requires an initialized full Project Observatory installation to persist
21
21
  records. Uses its MCP server when available in the current host; the bundled
@@ -48,14 +48,25 @@ and nothing in the ledger can tell it from a measured one.
48
48
 
49
49
  ## Recording it
50
50
 
51
- The Observatory's Stop hook has already stored the facts as a `proposed` record
52
- and printed its id. Correct that record rather than creating a second one:
51
+ The Observatory's Stop hook has already stored the facts as a `proposed` record.
52
+ Its id arrives once, as context with the next prompt. There is no ask when, within
53
+ 2 seconds of the record being written, the hook finds in the last 4 MB of this
54
+ session's transcript an Edit, Write, MultiEdit, NotebookEdit or Bash write (`>`,
55
+ `>>`, `tee`, `sed -i`) that targeted a `decisions.md` or an `adr/` `.md`,
56
+ `.markdown` or `.txt` file (outside vendored trees) with the entry's own
57
+ heading (unique in the register) or, with no heading, most of its added lines, or that wrote a `decision:` /
58
+ `stage:` line into `.task-pipeline/run.md`. Failed calls, subagent calls and
59
+ Bash commands over 65,536 characters are not counted. The record then links
60
+ that file, and keeps the link while the entry's exact heading line occurs once
61
+ and the file is unchanged, or has only a new entry appended after it; any other
62
+ edit to the file asks again.
63
+ Correct that record rather than creating a second one:
53
64
 
54
65
  ```
55
66
  observatory_record(
56
67
  owner="agent:claude-code",
57
- memory_id="<the id the hook printed>",
58
- expected_revision=<the revision it printed>,
68
+ memory_id="<the id the hook gave>",
69
+ expected_revision=<the revision it gave>,
59
70
  statement="<the same claim, unchanged>",
60
71
  why="<one or two sentences>"
61
72
  )
@@ -10,7 +10,7 @@ description: >-
10
10
  provider permissions or choosing a project's authentication architecture.
11
11
  license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
12
12
  metadata:
13
- version: "0.18.0"
13
+ version: "0.26.0"
14
14
  compatibility: >-
15
15
  Requires an initialized full Project Observatory installation, Python 3.11+
16
16
  and local shell access on macOS or Linux. Provider operations additionally
@@ -30,7 +30,7 @@ names. Never ask the user to paste a credential into the conversation.
30
30
  2. Select the user's initialized `OBSERVATORY_HOME`. Run
31
31
  `project-observatory full doctor`. A missing workspace
32
32
  needs the documented onboarding before secret operations.
33
- 3. Run `"$OBSERVATORY_PYTHON" "$OBSERVATORY_ROOT/tools/skill_check.py" handling-secrets 0.18.0`
33
+ 3. Run `"$OBSERVATORY_PYTHON" "$OBSERVATORY_ROOT/tools/skill_check.py" handling-secrets 0.26.0`
34
34
  (`full agent install` records that interpreter; the stock macOS `python3` is too old).
35
35
  If stale, read the installed skill once and follow its compatible commands.
36
36
  Do not turn an unavailable version check into a retry loop.
@@ -53,8 +53,8 @@ for it. A name or note shaped like a credential is refused, never stored.
53
53
  | List slots | `vault.py list PROJECT ENV` |
54
54
  | Run a command using a slot | `use_secret.py run [--env ENV] [--vault-only] PROJECT NAME -- COMMAND ARGUMENTS` (flags before PROJECT) |
55
55
  | Receive a value from another local command | `use_secret.py pipe NAME -- COMMAND ARGUMENTS` |
56
- | Bind a slot to the one MCP server allowed to receive it as a header (the operator, once) | `vault.py bind PROJECT ENV NAME --header-for https://HOST[:PORT]` (a bare host means https); `--clear` removes it; both are journalled |
57
- | Give an HTTP MCP server its bearer through Claude Code's `headersHelper` | `use_secret.py header [--env ENV] [--name Authorization] [--scheme Bearer] PROJECT NAME`, written into the server's `headersHelper` entry, never run by hand |
56
+ | Bind a slot to the one MCP server allowed to receive it as a header (the operator, once) | `vault.py bind PROJECT ENV NAME --header-for https://HOST[:PORT]` (a bare host means https), or `http://127.0.0.1:PORT` / `http://[::1]:PORT` for a loopback server (`localhost` is refused); `--clear` removes it; both are journalled |
57
+ | Give an HTTP MCP server its bearer through the client's header helper | `use_secret.py header [--env ENV] [--name Authorization] [--scheme Bearer] PROJECT NAME`, written into Claude Code's `headersHelper` entry; for Codex's `http_headers_helper` add `--server-url URL`, since Codex exports no server URL. Never run by hand |
58
58
  | Populate a project's ignored environment file | `vault.py inject PROJECT ENV DIRECTORY` |
59
59
  | Record an exposure | `vault.py leak PROJECT ENV NAME --where "location and evidence, no value"` |
60
60
  | Replace a stored value | `vault.py rotate PROJECT ENV NAME`, replacement on stdin |
@@ -80,14 +80,16 @@ not a sandbox: a child process can encode a value, transmit it, or write it to
80
80
  another file. Only run the command authorized by the task. Do not claim this
81
81
  filter prevents every leak.
82
82
 
83
- **The header door prints a value, so it serves only Claude Code.** `use_secret.py
83
+ **The header door prints a value, so it serves only a client's header helper.** `use_secret.py
84
84
  header` writes one JSON object (`{"Authorization": "Bearer …"}`) for a
85
85
  `headersHelper`, which Claude Code merges into the MCP request rather than the
86
86
  conversation. It reads the vault only (never an env file or the environment), and
87
- refuses unless stdout is a program, `CLAUDE_CODE_MCP_SERVER_URL` names the scheme,
88
- host and port the slot is bound to, and the value has no control character. Never
89
- run it yourself, pipe it, or set `CLAUDE_CODE_MCP_SERVER_URL` to make it answer:
90
- that prints the value into your transcript. Each call is audited. Binding is the
87
+ refuses unless stdout is a program, `CLAUDE_CODE_MCP_SERVER_URL` (or `--server-url`,
88
+ for a client that exports none) names the scheme, host and port the slot is bound to,
89
+ and the value has no control character; two URLs that disagree are refused
90
+ (`url-conflict`). Never run it yourself, pipe it, or set `CLAUDE_CODE_MCP_SERVER_URL` or
91
+ `--server-url` to make it answer: that prints the value into your transcript. Each call
92
+ is audited, with the caller it claimed. Binding is the
91
93
  operator's decision; ask for it, do not run `bind` to make a call succeed.
92
94
 
93
95
  **Stdin carries one thing.** Never pipe a secret into `python3 -`, `node -` or
@@ -126,12 +128,77 @@ exposure and record it with `vault.py leak`. The design is
126
128
 
127
129
  ## Provider integrations
128
130
 
129
- Cloudflare and OpenRouter have separate tools, `cloudflare.py` and
130
- `openrouter.py`. Check their installed `--help` and non-secret inventory first.
131
+ **A provider's MCP tool can be the leak.** Some tools return a credential in their
132
+ answer by design, whatever you asked them for: DigitalOcean's `db-cluster-list`,
133
+ `db-cluster-get`, `db-cluster-*-user`, `docr-docker-credentials`, `doks-get-kubeconfig`,
134
+ `spaces-key-create`. The transcript keeps a tool result whole; it cannot be redacted
135
+ afterwards: a `db-cluster-list` call asked for names can return every cluster's
136
+ passwords. Do not call such a tool; get names from the provider CLI with an
137
+ explicit `--format`, and credentials through the door below. Record any sighting with
138
+ `vault.py leak` and rotate.
139
+
140
+ DigitalOcean managed databases go through `digitalocean.py` in the operator's `doctl`
141
+ context. `db list` and `db consumers CLUSTER` print metadata only; consumer discovery covers
142
+ App Platform bindings, not every possible consumer. `db credential CLUSTER USER --db NAME
143
+ --to vault:PROJECT/ENV/NAME` puts the connection URL in the vault on stdin.
144
+
145
+ DigitalOcean rotation verifies every selected `--spec-env APP:KEY` literal before any reset.
146
+ Its name is not evidence: a plain value must equal the selected database user's current
147
+ provider password, or be a database URL with that password, user, engine and the provider's
148
+ connection host/port. A `${…}` binding is never rewritten, at any level; `--db` must be one of
149
+ the cluster's databases, and `db credential` checks `--db` the same way before it reads a
150
+ password (a cluster the provider lists no databases for, and redis/valkey, refuse `--db` with
151
+ their own reason). Opaque encrypted values and unverified pool endpoints are refused;
152
+ migrate those consumers to App Platform database bindings before rotation. No command in
153
+ this flow prints the current password. Only the named consumers are moved, with downtime
154
+ between reset and ACTIVE; ACTIVE is not application health verification.
155
+
156
+ The door writes an owner-only receipt under `store/digitalocean-rotations/` before reset,
157
+ initially `reset: unknown`. A lost or malformed response, timeout or nonzero CLI result can
158
+ follow a committed reset: exit 5 means **unknown**, not “nothing changed”. Never repeat
159
+ `db rotate` to recover it. Use `db credential` to deliver the current value into the selected
160
+ vault slot, then reconcile each consumer without another reset. Exit 1 is a refusal before
161
+ reset; after an attempt, 2 means deployment unconfirmed, 3 vault delivery failed, 4 an app
162
+ update/redeploy or temporary spec cleanup failed, and 6 receipt update failed. The largest
163
+ code wins. Independent consumers continue after a confirmed reset despite another consumer's
164
+ failure. Provider/vault error bodies are withheld. A crash or failed final receipt write
165
+ leaves the earlier uncertain receipt; there is no automatic reconciliation, retry or claim
166
+ of a transaction across provider and vault. A failed temporary-file cleanup may leave its
167
+ owner-only spec file and is reported for local recovery.
168
+
169
+ Reset, app updates and redeploys each make one attempt; only read calls retry.
170
+ A lost/nonzero mutation response leaves that consumer `unknown`, even if the provider
171
+ already applied it. The receipt distinguishes this from `not_started` for a local
172
+ pre-call failure. Read current spec and deployment state before any retry; a blind
173
+ redeploy is not recovery for a literal. Independent peers still continue. Deployment
174
+ confirmation accepts a validated phase equal to `ACTIVE`, never a suffix or arbitrary
175
+ provider text; malformed metadata remains unconfirmed and its body is not printed.
176
+
177
+
178
+ Cloudflare, OpenRouter and Heroku have separate tools, `cloudflare.py`,
179
+ `openrouter.py` and `heroku.py`. Check their installed `--help` and non-secret inventory first.
180
+ A Heroku credential is `heroku.py issue --preset read|deploy|identity --vault
181
+ <project>/<env>/<NAME> [--team <team>] [--app <app>]`: an OAuth authorization with that
182
+ scope only, proved and delivered on stdin. Heroku cannot narrow one to a team or an app,
183
+ so prefer `read` and name the app. Team and account membership (`heroku.py member`,
184
+ `cloudflare.py member`) is a printed plan until `--apply`; apply only a change the task asked for.
131
185
  They require an explicitly configured integration and the user's own admin or
132
186
  provisioning credential. They do not make a new user inherit the author's
133
187
  accounts, budgets or tokens. Limit issuance and revocation to the task's scope.
134
188
 
189
+ A project's own OpenRouter keys come from its spend policy: `openrouter.py
190
+ issue-for-policy --policy FILE [--dry-run]` keeps their ceilings together within
191
+ the project limit and exits 1 with `OVER LIMIT by $X now ($A against the $L
192
+ limit); it ends after: …` when they are not. `rotate` is the one window past the
193
+ limit: both keys are live while it runs, and until the J-3 change a rotate that
194
+ fails can leave the successor `NAME-next` untracked or the predecessor uncounted —
195
+ delete at the provider the one the slot does not hold, then run
196
+ `issue-for-policy`. A pending row with no hash on an account past the listing is
197
+ cleared with `revoke NAME --forget-pending --reason "…"` once its key is gone. The door must
198
+ record the reason as a pre-action attestation before clearing the row; an
199
+ unwritable journal refuses the change. This is an operator assertion, not
200
+ provider verification. A separate completion event follows the ledger write.
201
+
135
202
  A project that must edit DNS in one zone (a custom domain, a CNAME to its host)
136
203
  gets `cloudflare.py issue --preset dns-edit --zone <zone> --vault <project>/<env>/<NAME>`:
137
204
  Zone Read and DNS Write on that zone only, verified against its records and
@@ -171,8 +238,11 @@ gets `cloudflare.py issue --preset email-send --account <slug> --vault
171
238
  group exists at account level only), verified by listing its suppressions.
172
239
  Adding an Email Routing rule in one zone — one address to a Worker — is
173
240
  `--preset email-routing --zone <zone>`, and deploying that Worker with its KV
174
- namespace is `--preset workers-edit --account <slug>`. Each is its own token in
175
- its own slot. When a preset's groups are unknown, `cloudflare.py groups
241
+ namespace is `--preset workers-edit --account <slug>`; putting a Worker in front of
242
+ a hostname (`example.com/*`) is the zone right `--preset workers-routes --zone <zone>`;
243
+ sending one hostname to another at the edge (a Single Redirect, `www.` to the apex) is
244
+ the zone right `--preset redirect-rules --zone <zone>`.
245
+ Each is its own token in its own slot. When a preset's groups are unknown, `cloudflare.py groups
176
246
  --account <slug> --match "<words>"` lists the catalogue's names and levels,
177
247
  read-only and without ids.
178
248
 
@@ -15,7 +15,7 @@ description: >-
15
15
  the repository.
16
16
  license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
17
17
  metadata:
18
- version: "0.18.0"
18
+ version: "0.26.0"
19
19
  compatibility: >-
20
20
  Requires a full Project Observatory installation with organizations.json
21
21
  configured; the observatory MCP server or its local CLI. Creating the resource
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "passioncode",
4
4
  "displayName": "PassionCode.ai",
5
- "version": "0.2.15",
5
+ "version": "0.2.17",
6
6
  "description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check at session start that keeps the PassionCode.ai skill set current.",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",