@codyswann/lisa 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/all/copy-contents/gitignore +7 -0
- package/all/copy-overwrite/scripts/lisa-command-envelope.mjs +44 -4
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +31 -0
- package/all/copy-overwrite/scripts/lisa-schema-validate.mjs +125 -47
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +24 -2
- package/cdk/merge/.oxlintrc.json +1 -1
- package/dist/cli/apply.d.ts.map +1 -1
- package/dist/cli/apply.js +48 -13
- package/dist/cli/apply.js.map +1 -1
- package/dist/cli/doctor-apply-freshness.d.ts +29 -0
- package/dist/cli/doctor-apply-freshness.d.ts.map +1 -0
- package/dist/cli/doctor-apply-freshness.js +194 -0
- package/dist/cli/doctor-apply-freshness.js.map +1 -0
- package/dist/cli/doctor-learnings-merge-driver.d.ts +23 -0
- package/dist/cli/doctor-learnings-merge-driver.d.ts.map +1 -0
- package/dist/cli/doctor-learnings-merge-driver.js +73 -0
- package/dist/cli/doctor-learnings-merge-driver.js.map +1 -0
- package/dist/cli/doctor-readiness-workflows.js +2 -2
- package/dist/cli/doctor-readiness-workflows.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +13 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/ui-ci-quality-jobs-parse.js +2 -2
- package/dist/cli/ui-ci-quality-jobs-parse.js.map +1 -1
- package/dist/cli/ui-deploy-pipeline-model.d.ts +4 -0
- package/dist/cli/ui-deploy-pipeline-model.d.ts.map +1 -1
- package/dist/cli/ui-deploy-pipeline-model.js +2 -2
- package/dist/cli/ui-deploy-pipeline-model.js.map +1 -1
- package/dist/codex/agent-transformer.js +2 -2
- package/dist/codex/agent-transformer.js.map +1 -1
- package/dist/codex/command-skill-transformer.js +3 -3
- package/dist/codex/command-skill-transformer.js.map +1 -1
- package/dist/codex/scripts/block-no-verify.sh +53 -4
- package/dist/core/apply-receipt.d.ts +68 -0
- package/dist/core/apply-receipt.d.ts.map +1 -0
- package/dist/core/apply-receipt.js +107 -0
- package/dist/core/apply-receipt.js.map +1 -0
- package/dist/core/learnings-merge-driver-install.d.ts +34 -0
- package/dist/core/learnings-merge-driver-install.d.ts.map +1 -1
- package/dist/core/learnings-merge-driver-install.js +36 -0
- package/dist/core/learnings-merge-driver-install.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +62 -39
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.d.ts +33 -0
- package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.js +47 -5
- package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
- package/dist/migrations/ensure-oxlint-base-configs.d.ts +42 -0
- package/dist/migrations/ensure-oxlint-base-configs.d.ts.map +1 -0
- package/dist/migrations/ensure-oxlint-base-configs.js +212 -0
- package/dist/migrations/ensure-oxlint-base-configs.js.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +3 -0
- package/dist/migrations/index.js.map +1 -1
- package/dist/opencode/command-transformer.js +3 -3
- package/dist/opencode/command-transformer.js.map +1 -1
- package/dist/utils/yaml.d.ts +54 -0
- package/dist/utils/yaml.d.ts.map +1 -0
- package/dist/utils/yaml.js +124 -0
- package/dist/utils/yaml.js.map +1 -0
- package/expo/copy-overwrite/scripts/bdd/baseline.mjs +104 -19
- package/expo/copy-overwrite/scripts/bdd/contract.mjs +20 -1
- package/expo/copy-overwrite/scripts/bdd/discover.mjs +53 -10
- package/expo/copy-overwrite/scripts/bdd/parse.mjs +25 -3
- package/expo/copy-overwrite/scripts/bdd/render.mjs +2 -1
- package/expo/copy-overwrite/scripts/bdd/report.mjs +26 -12
- package/expo/copy-overwrite/scripts/bdd/validate.mjs +2 -2
- package/expo/copy-overwrite/scripts/bdd/waivers.mjs +19 -4
- package/expo/copy-overwrite/scripts/bdd-matrix.mjs +2 -2
- package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +58 -16
- package/expo/create-only/.github/required-checks.json +10 -4
- package/expo/merge/.oxlintrc.json +1 -1
- package/harper-fabric/merge/.oxlintrc.json +1 -1
- package/nestjs/create-only/.github/required-checks.json +57 -0
- package/nestjs/merge/.oxlintrc.json +1 -1
- package/package.json +1 -1
- package/phaser/merge/.oxlintrc.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +7 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-jam-access/SKILL.md +37 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +7 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-posthog-access/SKILL.md +40 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-sentry-access/SKILL.md +44 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-sonarcloud-access/SKILL.md +56 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa/hooks/block-no-verify.agy.sh +53 -4
- package/plugins/lisa/hooks/block-no-verify.sh +31 -0
- package/plugins/lisa/rules/eager/credential-substrate-precedence.md +3 -1
- package/plugins/lisa/rules/eager/tracked-work.md +1 -1
- package/plugins/lisa/rules/reference/credential-substrate-precedence.md +20 -0
- package/plugins/lisa/rules/reference/tracked-work.md +4 -2
- package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +7 -0
- package/plugins/lisa/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-jam-access/SKILL.md +37 -2
- package/plugins/lisa/skills/lisa-notion-access/SKILL.md +7 -0
- package/plugins/lisa/skills/lisa-posthog-access/SKILL.md +40 -3
- package/plugins/lisa/skills/lisa-sentry-access/SKILL.md +44 -3
- package/plugins/lisa/skills/lisa-sonarcloud-access/SKILL.md +56 -0
- package/plugins/lisa/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +53 -4
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +7 -0
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-jam-access/SKILL.md +37 -2
- package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +7 -0
- package/plugins/lisa-agy/skills/lisa-posthog-access/SKILL.md +40 -3
- package/plugins/lisa-agy/skills/lisa-sentry-access/SKILL.md +44 -3
- package/plugins/lisa-agy/skills/lisa-sonarcloud-access/SKILL.md +56 -0
- package/plugins/lisa-agy/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +31 -0
- package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +3 -1
- package/plugins/lisa-copilot/rules/eager/tracked-work.md +1 -1
- package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +20 -0
- package/plugins/lisa-copilot/rules/reference/tracked-work.md +4 -2
- package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +7 -0
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-jam-access/SKILL.md +37 -2
- package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +7 -0
- package/plugins/lisa-copilot/skills/lisa-posthog-access/SKILL.md +40 -3
- package/plugins/lisa-copilot/skills/lisa-sentry-access/SKILL.md +44 -3
- package/plugins/lisa-copilot/skills/lisa-sonarcloud-access/SKILL.md +56 -0
- package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +31 -0
- package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +20 -0
- package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +3 -1
- package/plugins/lisa-cursor/rules/tracked-work-reference.mdc +4 -2
- package/plugins/lisa-cursor/rules/tracked-work.mdc +1 -1
- package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +7 -0
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-jam-access/SKILL.md +37 -2
- package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +7 -0
- package/plugins/lisa-cursor/skills/lisa-posthog-access/SKILL.md +40 -3
- package/plugins/lisa-cursor/skills/lisa-sentry-access/SKILL.md +44 -3
- package/plugins/lisa-cursor/skills/lisa-sonarcloud-access/SKILL.md +56 -0
- package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-no-verify.agy.sh +53 -4
- package/plugins/src/base/hooks/block-no-verify.sh +31 -0
- package/plugins/src/base/rules/eager/credential-substrate-precedence.md +3 -1
- package/plugins/src/base/rules/eager/tracked-work.md +1 -1
- package/plugins/src/base/rules/reference/credential-substrate-precedence.md +20 -0
- package/plugins/src/base/rules/reference/tracked-work.md +4 -2
- package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +7 -0
- package/plugins/src/base/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-jam-access/SKILL.md +37 -2
- package/plugins/src/base/skills/lisa-notion-access/SKILL.md +7 -0
- package/plugins/src/base/skills/lisa-posthog-access/SKILL.md +40 -3
- package/plugins/src/base/skills/lisa-sentry-access/SKILL.md +44 -3
- package/plugins/src/base/skills/lisa-sonarcloud-access/SKILL.md +56 -0
- package/plugins/src/base/skills/lisa-track/SKILL.md +1 -1
- package/scripts/install-claude-plugins.sh +5 -1
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +298 -26
- package/typescript/create-only/.github/required-checks.json +11 -6
- package/typescript/create-only/.github/workflows/required-checks-drift.yml +84 -0
- package/typescript/merge/.oxlintrc.json +1 -1
|
@@ -40,15 +40,38 @@ works interactively and headlessly — which is why it leads. The REST tier uses
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
POSTHOG_HOST=${POSTHOG_HOST:-https://app.posthog.com}
|
|
43
|
+
|
|
44
|
+
# Resolve the key through the chokepoint before giving up on the environment.
|
|
45
|
+
# `$POSTHOG_PERSONAL_API_KEY` is the documented fallback, not the only rung:
|
|
46
|
+
# without this, a project that keeps its credentials in Bitwarden, Doppler, or
|
|
47
|
+
# AWS has no tier 1 path at all and silently resolves through the interactive
|
|
48
|
+
# MCP — the exact divergence `credential-substrate-precedence` exists to remove.
|
|
49
|
+
read_posthog_key() {
|
|
50
|
+
[ -n "${POSTHOG_PERSONAL_API_KEY:-}" ] && { echo "$POSTHOG_PERSONAL_API_KEY"; return; }
|
|
51
|
+
local resolver
|
|
52
|
+
for resolver in .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs \
|
|
53
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs; do
|
|
54
|
+
if [ -f "$resolver" ]; then
|
|
55
|
+
local via_lisa
|
|
56
|
+
via_lisa=$(node "$resolver" get POSTHOG_PERSONAL_API_KEY 2>/dev/null) \
|
|
57
|
+
&& [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
|
|
58
|
+
break
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
return 1
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
posthog_api() {
|
|
44
65
|
local path="$1"
|
|
45
66
|
local method="${2:-GET}"
|
|
46
67
|
local body="${3:-}"
|
|
47
|
-
|
|
48
|
-
|
|
68
|
+
local key
|
|
69
|
+
key=$(read_posthog_key) || {
|
|
70
|
+
echo "Error: no PostHog key. Set POSTHOG_PERSONAL_API_KEY, or store it as" >&2
|
|
71
|
+
echo "POSTHOG_PERSONAL_API_KEY in this project's secrets provider." >&2
|
|
49
72
|
return 1
|
|
50
73
|
}
|
|
51
|
-
local args=(-sS -X "$method" -H "Authorization: Bearer $
|
|
74
|
+
local args=(-sS -X "$method" -H "Authorization: Bearer $key")
|
|
52
75
|
[ -n "$body" ] && args+=(-H "Content-Type: application/json" --data-binary "$body")
|
|
53
76
|
curl "${args[@]}" "${POSTHOG_HOST%/}/api${path}"
|
|
54
77
|
}
|
|
@@ -60,11 +83,25 @@ If neither tier works, fail with:
|
|
|
60
83
|
Error: no PostHog access substrate available. Authenticate the PostHog MCP or set POSTHOG_PERSONAL_API_KEY.
|
|
61
84
|
```
|
|
62
85
|
|
|
86
|
+
## Mutation boundary
|
|
87
|
+
|
|
88
|
+
Every operation in the Invocation Contract is **read-only** — analytics
|
|
89
|
+
retrieval. `query` is an HTTP POST, but it reads: it submits a query body and
|
|
90
|
+
changes no PostHog state. So the `credential-substrate-precedence` guarded
|
|
91
|
+
fallback for mutating operations (write, read back, assert the tenant from the
|
|
92
|
+
response, roll back on mismatch) is not engaged here, and a failed tier is
|
|
93
|
+
simply skipped. Adding a genuinely mutating operation — creating an insight,
|
|
94
|
+
editing a feature flag — pulls that protocol in: a write of unknown outcome MUST
|
|
95
|
+
reconcile by read-back before any retry.
|
|
96
|
+
|
|
63
97
|
## Invariants
|
|
64
98
|
|
|
65
99
|
- Tier order is `credential-substrate-precedence`: `POSTHOG_PERSONAL_API_KEY`
|
|
66
100
|
first, the PostHog MCP as a preserved first-class fallback. Identity-match
|
|
67
101
|
against the configured project is mandatory on every tier.
|
|
102
|
+
- The key is resolved through `lisa-secrets-access`, with the bare
|
|
103
|
+
`POSTHOG_PERSONAL_API_KEY` environment variable as the documented fallback.
|
|
104
|
+
Never read a second credential store directly.
|
|
68
105
|
- `POSTHOG_HOST` defaults to PostHog Cloud but can point at a self-hosted
|
|
69
106
|
deployment.
|
|
70
107
|
- Consumer skills do not embed PostHog REST paths.
|
|
@@ -39,28 +39,69 @@ Sentry documents API auth tokens for REST API calls, and the same token works
|
|
|
39
39
|
interactively and headlessly — which is why it leads. The REST tier uses:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
+
# Resolve the token through the chokepoint before giving up on the environment.
|
|
43
|
+
# `$SENTRY_AUTH_TOKEN` is the documented fallback, not the only rung: without
|
|
44
|
+
# this, a project that keeps its credentials in Bitwarden, Doppler, or AWS has
|
|
45
|
+
# no tier 1 path at all and silently resolves through the interactive MCP —
|
|
46
|
+
# the exact divergence `credential-substrate-precedence` exists to remove.
|
|
47
|
+
# Mirrors `linear-access`, `atlassian-access`, and `notion-access`.
|
|
48
|
+
read_sentry_token() {
|
|
49
|
+
[ -n "${SENTRY_AUTH_TOKEN:-}" ] && { echo "$SENTRY_AUTH_TOKEN"; return; }
|
|
50
|
+
local resolver
|
|
51
|
+
for resolver in .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs \
|
|
52
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs; do
|
|
53
|
+
if [ -f "$resolver" ]; then
|
|
54
|
+
local via_lisa
|
|
55
|
+
via_lisa=$(node "$resolver" get SENTRY_AUTH_TOKEN 2>/dev/null) \
|
|
56
|
+
&& [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
|
|
57
|
+
break
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
sentry_api() {
|
|
43
64
|
local path="$1"
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
local token
|
|
66
|
+
token=$(read_sentry_token) || {
|
|
67
|
+
echo "Error: no Sentry auth token. Set SENTRY_AUTH_TOKEN, or store it as" >&2
|
|
68
|
+
echo "SENTRY_AUTH_TOKEN in this project's secrets provider." >&2
|
|
46
69
|
return 1
|
|
47
70
|
}
|
|
48
71
|
curl -sS "https://sentry.io/api/0${path}" \
|
|
49
|
-
-H "Authorization: Bearer $
|
|
72
|
+
-H "Authorization: Bearer $token"
|
|
50
73
|
}
|
|
51
74
|
```
|
|
52
75
|
|
|
76
|
+
Tier 1a authenticates `sentry-cli` from the **same resolved token**
|
|
77
|
+
(`SENTRY_AUTH_TOKEN=$(read_sentry_token) sentry-cli …`) — never from a second
|
|
78
|
+
credential store, and never from an interactive `sentry-cli login` keychain.
|
|
79
|
+
|
|
53
80
|
If neither tier works, fail with:
|
|
54
81
|
|
|
55
82
|
```text
|
|
56
83
|
Error: no Sentry access substrate available. Authenticate Sentry MCP/CLI or set SENTRY_AUTH_TOKEN.
|
|
57
84
|
```
|
|
58
85
|
|
|
86
|
+
## Mutation boundary
|
|
87
|
+
|
|
88
|
+
Every operation in the Invocation Contract is **read-only** — issue, event, and
|
|
89
|
+
release retrieval. So the `credential-substrate-precedence` guarded fallback for
|
|
90
|
+
mutating operations (write, read back, assert the tenant from the response, roll
|
|
91
|
+
back on mismatch) is not engaged here, and a failed tier is simply skipped. Any
|
|
92
|
+
future operation that mutates Sentry state — resolving an issue, editing a
|
|
93
|
+
release — is a write and MUST reconcile by read-back under that protocol before
|
|
94
|
+
any retry; do not add one to the contract without it.
|
|
95
|
+
|
|
59
96
|
## Invariants
|
|
60
97
|
|
|
61
98
|
- Tier order is `credential-substrate-precedence`: `SENTRY_AUTH_TOKEN` first, the
|
|
62
99
|
Sentry MCP as a preserved first-class fallback. Identity-match against the
|
|
63
100
|
configured org/project is mandatory on every tier.
|
|
101
|
+
- The token is resolved through `lisa-secrets-access`, with the bare
|
|
102
|
+
`SENTRY_AUTH_TOKEN` environment variable as the documented fallback. Never read
|
|
103
|
+
a second credential store (OS keychain, `~/.sentryclirc` token) directly — the
|
|
104
|
+
one-store rule lives at the chokepoint.
|
|
64
105
|
- Org/project come from `.sentryclirc`, `.lisa.config.json`, or explicit
|
|
65
106
|
operation args; never infer by searching all accessible orgs.
|
|
66
107
|
- Consumer skills do not embed Sentry REST paths.
|
|
@@ -34,6 +34,49 @@ SonarQube Cloud org. Do **not** substitute the raw MCP-image names
|
|
|
34
34
|
running the Docker image directly, and the `sonar` CLI ignores them (auth exits
|
|
35
35
|
non-zero). The CI scan gate's `SONAR_TOKEN` is a third, separate name.
|
|
36
36
|
|
|
37
|
+
### Where those variables come from
|
|
38
|
+
|
|
39
|
+
The `sonar` CLI reads them **from the environment only** — it has no provider
|
|
40
|
+
integration of its own — so on a surface that deliberately materializes nothing
|
|
41
|
+
to disk the credential can be provisioned and unreachable at the same time.
|
|
42
|
+
Resolve it through `lisa-secrets-access` and export it into the launching
|
|
43
|
+
process, exactly as the shared `sonar-secrets.sh` hook already does. The bare
|
|
44
|
+
`$SONARQUBE_CLI_TOKEN` in the environment is the documented **fallback** rung,
|
|
45
|
+
not the only one:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Preferred: the one sanctioned reader. Without this rung a project keeping its
|
|
49
|
+
# credentials in Bitwarden, Doppler, or AWS cannot authenticate the MCP at all,
|
|
50
|
+
# and Sonar access degrades to "run `sonar auth login` in a browser" — dead in
|
|
51
|
+
# cron, CI, and cloud sessions. See `credential-substrate-precedence`.
|
|
52
|
+
read_sonar_secret() {
|
|
53
|
+
local name="$1"
|
|
54
|
+
[ -n "${!name:-}" ] && { echo "${!name}"; return; }
|
|
55
|
+
local resolver
|
|
56
|
+
for resolver in .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs \
|
|
57
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs; do
|
|
58
|
+
if [ -f "$resolver" ]; then
|
|
59
|
+
local via_lisa
|
|
60
|
+
via_lisa=$(node "$resolver" get "$name" 2>/dev/null) \
|
|
61
|
+
&& [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
|
|
62
|
+
break
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
65
|
+
return 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Exported into this process only. Writing it anywhere durable would create a
|
|
69
|
+
# second live copy of a credential whose single store is the provider.
|
|
70
|
+
SONARQUBE_CLI_TOKEN=$(read_sonar_secret SONARQUBE_CLI_TOKEN) && export SONARQUBE_CLI_TOKEN
|
|
71
|
+
SONARQUBE_CLI_ORG=$(read_sonar_secret SONARQUBE_CLI_ORG) && export SONARQUBE_CLI_ORG
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`sonar-secrets.sh` runs the same resolution with a longer search path (it also
|
|
75
|
+
probes `.codex/skills/…` and `node_modules/@codyswann/lisa/plugins/lisa/…`,
|
|
76
|
+
because a hook fires in checkouts that never installed an agent plugin) and with
|
|
77
|
+
a timeout, because it sits in front of every prompt. Use its search order
|
|
78
|
+
verbatim when this skill runs inside a hook.
|
|
79
|
+
|
|
37
80
|
Wiring is performed once by `/lisa:setup:sonar` (which drives `sonar integrate
|
|
38
81
|
<agent>`); this access layer assumes the MCP is already wired. This is distinct
|
|
39
82
|
from the CI `SONAR_TOKEN` secret that authenticates the SonarCloud scan job in
|
|
@@ -79,9 +122,22 @@ Pass the project key through the tool's `projectKey` argument (or rely on a
|
|
|
79
122
|
server-configured `SONARQUBE_PROJECT_KEY`); pass `branch` / `pullRequest` where the
|
|
80
123
|
tool accepts them.
|
|
81
124
|
|
|
125
|
+
## Mutation boundary
|
|
126
|
+
|
|
127
|
+
Every operation in the map above is **read-only** — quality, coverage, and
|
|
128
|
+
security data. So the `credential-substrate-precedence` guarded fallback for
|
|
129
|
+
mutating operations (write, read back, assert the tenant from the response, roll
|
|
130
|
+
back on mismatch) is not engaged here; with one substrate there is nothing to
|
|
131
|
+
fall back to in any case. A future operation that mutates Sonar state — marking
|
|
132
|
+
a hotspot safe, changing an issue's status — is a write and MUST reconcile by
|
|
133
|
+
read-back before any retry.
|
|
134
|
+
|
|
82
135
|
## Invariants
|
|
83
136
|
|
|
84
137
|
- The official SonarQube MCP is the only substrate; there is no REST fallback.
|
|
138
|
+
- `SONARQUBE_CLI_*` values are resolved through `lisa-secrets-access` and
|
|
139
|
+
exported in-process, with the bare environment variables as the documented
|
|
140
|
+
fallback. Never write them to a dotfile or a `.env` on a local surface.
|
|
85
141
|
- Auth is env-var only (`SONARQUBE_CLI_TOKEN` [+ `SONARQUBE_CLI_ORG` | `SONARQUBE_CLI_SERVER`]);
|
|
86
142
|
never the interactive `sonar auth login` keychain flow inside a factory.
|
|
87
143
|
- Sonar host access requires the host (`sonarcloud.io`, `sonarqube.us`, or the
|
|
@@ -47,7 +47,7 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
|
|
|
47
47
|
2. Persist only the canonical reference in worktree-local machine state:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
node scripts/lisa-work-item.mjs
|
|
50
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
3. Read the binding back through `node scripts/lisa-work-item.mjs current` and require it to equal the canonical reference. If binding fails, stop before durable project work.
|
|
@@ -102,16 +102,65 @@ def disables_verification(token):
|
|
|
102
102
|
and NO_VERIFY.startswith(token)
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
+
# The only relocations that keep a repo's own hooks in play. Anything else —
|
|
106
|
+
# including "" and /dev/null, which are simply the two most obvious members of
|
|
107
|
+
# the blocked set rather than special cases — is refused.
|
|
108
|
+
PERMITTED_HOOKS_PATHS = {".husky", ".githooks"}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_permitted_hooks_path(value):
|
|
112
|
+
"""Whether a core.hooksPath value relocates hooks rather than disabling them.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
value: The raw core.hooksPath value as it appeared on the command line.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if the path is an established in-repo hooks directory.
|
|
119
|
+
"""
|
|
120
|
+
cleaned = value.strip().strip("'\"")
|
|
121
|
+
if cleaned.startswith("./"):
|
|
122
|
+
cleaned = cleaned[2:]
|
|
123
|
+
return cleaned.rstrip("/") in PERMITTED_HOOKS_PATHS
|
|
124
|
+
|
|
125
|
+
|
|
105
126
|
for i, token in enumerate(normalized_tokens):
|
|
106
127
|
if disables_verification(token):
|
|
107
128
|
sys.exit(1)
|
|
108
129
|
if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
|
|
109
130
|
sys.exit(1)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
# Allowlist the destinations, do not denylist the disabling ones: hooks are
|
|
132
|
+
# disabled just as completely by any directory that happens to contain none
|
|
133
|
+
# (`-c core.hooksPath=/tmp/empty`), so the set that DISABLES hooks is
|
|
134
|
+
# unbounded while the set that legitimately relocates them is tiny. Matched
|
|
135
|
+
# case-insensitively because git config names are.
|
|
136
|
+
lowered = token.lower()
|
|
137
|
+
if lowered.startswith("core.hookspath="):
|
|
138
|
+
if not is_permitted_hooks_path(token.split("=", 1)[1]):
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
if lowered == "core.hookspath" and i + 1 < len(normalized_tokens):
|
|
141
|
+
if not is_permitted_hooks_path(normalized_tokens[i + 1]):
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
# `--config-env=core.hooksPath=SOMEVAR` reads the path out of the named env
|
|
144
|
+
# var, so it is not in the command at all and cannot be allowlisted.
|
|
145
|
+
if lowered.startswith("--config-env="):
|
|
146
|
+
spec = token.split("=", 1)[1]
|
|
147
|
+
if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
# git also accepts `--config-env <name>=<envvar>` as two tokens. Guarding
|
|
150
|
+
# only the `=` spelling let the trailing `core.hooksPath=.husky` fall
|
|
151
|
+
# through to the allowlist above, which reads `.husky` as a path — but here
|
|
152
|
+
# it names an ENVIRONMENT VARIABLE, which can hold /dev/null. Checked at the
|
|
153
|
+
# `--config-env` token, which the loop reaches first.
|
|
154
|
+
if lowered == "--config-env" and i + 1 < len(normalized_tokens):
|
|
155
|
+
spec = normalized_tokens[i + 1]
|
|
156
|
+
if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
|
|
113
157
|
sys.exit(1)
|
|
114
|
-
|
|
158
|
+
# `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
|
|
159
|
+
# GIT_CONFIG_VALUE_0=/dev/null git ...` sets the same command-scope config
|
|
160
|
+
# via env-var-style assignments. The index is arbitrary below
|
|
161
|
+
# GIT_CONFIG_COUNT, so it is matched as `\d+` rather than pinned to 0.
|
|
162
|
+
key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
|
|
163
|
+
if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
|
|
115
164
|
sys.exit(1)
|
|
116
165
|
|
|
117
166
|
sys.exit(0)
|
|
@@ -186,6 +186,37 @@ for i, token in enumerate(normalized_tokens):
|
|
|
186
186
|
spec = token.split("=", 1)[1]
|
|
187
187
|
if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
|
|
188
188
|
sys.exit(1)
|
|
189
|
+
# git accepts `--config-env <name>=<envvar>` as TWO tokens as well as one,
|
|
190
|
+
# and guarding only the `=` spelling was worse than missing the separate
|
|
191
|
+
# form outright: the trailing `core.hooksPath=.husky` then fell through to
|
|
192
|
+
# the allowlist above, which reads `.husky` as a PATH and permits it. But
|
|
193
|
+
# here it is an ENVIRONMENT VARIABLE NAME, and `env '.husky=/dev/null' git
|
|
194
|
+
# --config-env core.hooksPath=.husky` really does resolve hooksPath to
|
|
195
|
+
# /dev/null. The allowlist was being used as the bypass.
|
|
196
|
+
#
|
|
197
|
+
# Checked at the `--config-env` token, which the loop reaches first, so the
|
|
198
|
+
# refusal happens before the value token can be mistaken for a path.
|
|
199
|
+
if lowered == "--config-env" and i + 1 < len(normalized_tokens):
|
|
200
|
+
spec = normalized_tokens[i + 1]
|
|
201
|
+
if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
# `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
|
|
204
|
+
# GIT_CONFIG_VALUE_0=/dev/null git commit` sets command-scope config the
|
|
205
|
+
# same way `-c core.hooksPath=...` does — env-var-style assignments ahead of
|
|
206
|
+
# the invocation instead of a flag — so it disables every hook just as
|
|
207
|
+
# completely while matching none of the token shapes above. Upstream missed
|
|
208
|
+
# this until a downstream fork hardened its own copy against it, which is
|
|
209
|
+
# the one direction a guard must never be caught in.
|
|
210
|
+
#
|
|
211
|
+
# The index is matched as `\d+` rather than pinned to 0: git accepts any
|
|
212
|
+
# index below GIT_CONFIG_COUNT, so a single-index check is evaded by typing
|
|
213
|
+
# a 1. Refused outright, like --config-env=, because the path lives in a
|
|
214
|
+
# separate GIT_CONFIG_VALUE_<n> token that can be exported earlier,
|
|
215
|
+
# reordered, or left out entirely — there is nothing here to allowlist
|
|
216
|
+
# against.
|
|
217
|
+
key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
|
|
218
|
+
if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
|
|
219
|
+
sys.exit(1)
|
|
189
220
|
|
|
190
221
|
sys.exit(0)
|
|
191
222
|
PY
|
|
@@ -20,7 +20,9 @@ scope for that skill's work.
|
|
|
20
20
|
`lisa-secrets-access`, chosen whenever its bootstrap credential is available **and**
|
|
21
21
|
the resolved substrate identity-matches the configured tenant/workspace/site.
|
|
22
22
|
`lisa-secrets-access` is the single chokepoint — never read an OS keychain a second
|
|
23
|
-
time.
|
|
23
|
+
time. The two legacy OS-keychain rungs that remain (`lisa-atlassian-access`,
|
|
24
|
+
`lisa-notion-access`) are a dated migration ramp with a **removal date of
|
|
25
|
+
2026-11-01**, not a standing exemption; no new access skill may add one.
|
|
24
26
|
2. **Tier 2 — interactive MCP**, used only when tier 1 is *genuinely* unavailable:
|
|
25
27
|
no bootstrap, no adapter for the operation (per-operation, not per-session), or a
|
|
26
28
|
provider outage. "The MCP happens to be authenticated" and "tier 1 is slower" are
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Before the first durable project mutation (code, tests, config, docs, committed research/plans/findings, commits, or PRs), establish exactly one live tracker leaf through `lisa-track`. Read-only discussion and orientation are exempt only while they produce no durable artifact.
|
|
4
4
|
|
|
5
|
-
The mandatory order is: live-validate an explicit ref, or conservatively search and create exactly one valid leaf through `lisa-tracker-write` when no unique match exists; idempotently claim it through `lisa-tracker-claim`; then persist and verify the worktree-local binding with `node scripts/lisa-work-item.mjs
|
|
5
|
+
The mandatory order is: live-validate an explicit ref, or conservatively search and create exactly one valid leaf through `lisa-tracker-write` when no unique match exists; idempotently claim it through `lisa-tracker-claim`; then persist and verify the worktree-local binding with `node scripts/lisa-work-item.mjs link <ref>`. A tracker that answers no, and any claim or binding failure, blocks durable work; a tracker that cannot be reached does not block a commit — the offline checks still run, the skip is loud on stderr, and the required `Work-Item Traceability` check re-runs the live checks with credentials before anything merges.
|
|
6
6
|
|
|
7
7
|
Carry that canonical ref through the branch, every ordinary commit's `Work-Item:` trailer, the PR, usage/evidence, and `lisa-tracker-sync`. Hooks and CI never create tickets. Keep the binding through interruptions or blocked outcomes; run `node scripts/lisa-work-item.mjs clear` only after merge/deploy/verification, two-way linkage/evidence, and the tracker item have all reached true terminal completion.
|
|
@@ -139,6 +139,26 @@ operation, the fallback is guarded — never the normal path:
|
|
|
139
139
|
A successful pre-flight switch is not sufficient for tenant safety — another process can
|
|
140
140
|
mutate global state between the check and the write.
|
|
141
141
|
|
|
142
|
+
## Legacy OS-keychain fallback — removal date 2026-11-01
|
|
143
|
+
|
|
144
|
+
Two access skills (`lisa-atlassian-access`, `lisa-notion-access`) still read an
|
|
145
|
+
OS keychain as a last rung below the chokepoint, because the guided
|
|
146
|
+
`/lisa:setup:atlassian` and `/lisa:setup:notion` flows wrote credentials there
|
|
147
|
+
before `lisa-secrets-access` existed. That rung is a **migration ramp with a
|
|
148
|
+
removal date, not a standing exemption**: it is a second reader of the same
|
|
149
|
+
credential, which is exactly how one credential ends up living in two places and
|
|
150
|
+
drifting, and a keychain entry is machine-local ambient state that no headless
|
|
151
|
+
surface can reach — so a project resting on it has no working tier 1 in cron, CI,
|
|
152
|
+
or a cloud session.
|
|
153
|
+
|
|
154
|
+
**Both rungs are deleted on 2026-11-01.** Before then, projects still on the
|
|
155
|
+
keychain path move the credential into their configured provider — re-running
|
|
156
|
+
`/lisa:setup:<vendor>` stores it through the chokepoint. After removal, a
|
|
157
|
+
keychain-only project fails loudly with the exact variable named (tier 3), which
|
|
158
|
+
is the intended outcome: it can never silently resolve through a substrate
|
|
159
|
+
authenticated elsewhere. No new access skill may add a keychain rung; a credential
|
|
160
|
+
the chokepoint cannot answer for is a setup gap to fix, not a store to add.
|
|
161
|
+
|
|
142
162
|
## Consequences to expect
|
|
143
163
|
|
|
144
164
|
- **A stale or wrong token now fails identity-match instead of silently succeeding
|
|
@@ -9,9 +9,11 @@ Use `lisa-track` as the single entry point:
|
|
|
9
9
|
1. An explicit ticket is live-read through `lisa-tracker-read` and rejected if it is missing, inaccessible, terminal, a container, outside the configured project, or outside the current repository.
|
|
10
10
|
2. A plain-text request or specification file is searched conservatively within the configured project. Reuse only one uniquely high-confidence matching live leaf. If no unique match exists, create exactly one complete single-repository leaf through `lisa-tracker-write`; never create a thin placeholder or a container.
|
|
11
11
|
3. Idempotently claim the resolved leaf through `lisa-tracker-claim`, which reuses the vendor build-intake claim semantics and post-read verifies the claimed-or-later state.
|
|
12
|
-
4. Before any durable repository work, persist the canonical reference with `node scripts/lisa-work-item.mjs
|
|
12
|
+
4. Before any durable repository work, persist the canonical reference with `node scripts/lisa-work-item.mjs link <ref>` and verify the worktree-local binding.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
`link` is the spelling to use. `bind` is accepted as a permanent alias for the identical operation, but some agent harnesses — Claude Code's worktree isolation among them — refuse any command line containing the bare token `bind`, because it names a shell builtin that evaluates a string. Inside an isolated worktree that refusal makes the `bind` spelling unrunnable, so reach for `link` and never work around a blocked binding by hand-writing `Work-Item:` trailers.
|
|
15
|
+
|
|
16
|
+
The sequence is strict: **live validate/create -> claim -> link -> durable work**. A tracker answer of no, and any claim or binding failure, blocks the work. A tracker that cannot be reached — `gh` absent, or its credential refused — does not block a commit: the offline checks still run, the skip is loud on stderr, and the required `Work-Item Traceability` check re-runs the live checks with credentials before anything merges. Any other tracker failure still blocks. Tool presence or stale session text is not access.
|
|
15
17
|
|
|
16
18
|
## One canonical identity
|
|
17
19
|
|
|
@@ -69,6 +69,13 @@ read_atlassian_token() {
|
|
|
69
69
|
# Legacy fallback: the OS keychain written by the guided /lisa:setup:atlassian
|
|
70
70
|
# flow, for projects that have not adopted a credentials provider. Reached only
|
|
71
71
|
# when the chokepoint is absent or has no entry.
|
|
72
|
+
#
|
|
73
|
+
# This rung is REMOVED ON 2026-11-01 — a dated migration ramp, not a standing
|
|
74
|
+
# exemption (see credential-substrate-precedence, "Legacy OS-keychain fallback
|
|
75
|
+
# — removal date"). A keychain entry is machine-local ambient state no headless
|
|
76
|
+
# surface can reach, so a project resting on it has no working tier 1 in cron,
|
|
77
|
+
# CI, or a cloud session. Re-run /lisa:setup:atlassian before that date to
|
|
78
|
+
# store ATLASSIAN_API_TOKEN through the chokepoint instead.
|
|
72
79
|
case "$(uname -s)" in
|
|
73
80
|
Darwin) security find-generic-password -s lisa-atlassian -a "$email" -w 2>/dev/null ;;
|
|
74
81
|
Linux) command -v secret-tool >/dev/null && secret-tool lookup service lisa-atlassian account "$email" 2>/dev/null ;;
|
|
@@ -64,7 +64,7 @@ The input-resolver invokes `lisa-track $ARGUMENTS` and owns its complete resolve
|
|
|
64
64
|
- **Bind before durable work:** only after the verified claim, run:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
node scripts/lisa-work-item.mjs
|
|
67
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Require a successful readback of that worktree-local binding. On detached HEAD, `branch: null` is the expected pending binding; after branch creation the mandatory `attach-branch` step below must replace it before any commit. Tracker or binding failure stops the flow; never continue untracked.
|
|
@@ -39,20 +39,55 @@ headlessly — which is why it leads. The CLI tier uses:
|
|
|
39
39
|
```bash
|
|
40
40
|
curl -fsSL https://native.jam.dev/install | bash
|
|
41
41
|
export PATH="$HOME/.local/bin:$PATH"
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
# Resolve the PAT through the chokepoint before giving up on the environment.
|
|
44
|
+
# `$JAM_PAT` is the documented fallback, not the only rung: without this, a
|
|
45
|
+
# project that keeps its credentials in Bitwarden, Doppler, or AWS has no tier 1
|
|
46
|
+
# path at all and silently resolves through the interactive MCP — the exact
|
|
47
|
+
# divergence `credential-substrate-precedence` exists to remove.
|
|
48
|
+
read_jam_pat() {
|
|
49
|
+
[ -n "${JAM_PAT:-}" ] && { echo "$JAM_PAT"; return; }
|
|
50
|
+
local resolver
|
|
51
|
+
for resolver in .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs \
|
|
52
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs; do
|
|
53
|
+
if [ -f "$resolver" ]; then
|
|
54
|
+
local via_lisa
|
|
55
|
+
via_lisa=$(node "$resolver" get JAM_PAT 2>/dev/null) \
|
|
56
|
+
&& [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
|
|
57
|
+
break
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Piped, never written to disk and never passed as an argv token: a PAT on a
|
|
64
|
+
# command line is readable from the process table by every user on the host.
|
|
65
|
+
read_jam_pat | jam auth login --token
|
|
43
66
|
jam skills install
|
|
44
67
|
```
|
|
45
68
|
|
|
46
69
|
If neither tier works, fail with:
|
|
47
70
|
|
|
48
71
|
```text
|
|
49
|
-
Error: no Jam access substrate available. Authenticate the Jam MCP or
|
|
72
|
+
Error: no Jam access substrate available. Authenticate the Jam MCP, set JAM_PAT, or store JAM_PAT in this project's secrets provider.
|
|
50
73
|
```
|
|
51
74
|
|
|
75
|
+
## Mutation boundary
|
|
76
|
+
|
|
77
|
+
Every operation in the Invocation Contract is **read-only** — fetching a trace,
|
|
78
|
+
a recording, or a bug report. So the `credential-substrate-precedence` guarded
|
|
79
|
+
fallback for mutating operations (write, read back, assert the tenant from the
|
|
80
|
+
response, roll back on mismatch) is not engaged here, and a failed tier is
|
|
81
|
+
simply skipped. A future operation that mutates Jam state — commenting on or
|
|
82
|
+
deleting a Jam — is a write and MUST reconcile by read-back before any retry.
|
|
83
|
+
|
|
52
84
|
## Invariants
|
|
53
85
|
|
|
54
86
|
- Tier order is `credential-substrate-precedence`: `JAM_PAT` CLI first, Jam MCP as
|
|
55
87
|
a preserved first-class fallback. Do not retry a failed tier blindly.
|
|
88
|
+
- The PAT is resolved through `lisa-secrets-access`, with the bare `JAM_PAT`
|
|
89
|
+
environment variable as the documented fallback. Never read a second
|
|
90
|
+
credential store directly.
|
|
56
91
|
- Never commit a Jam PAT into `.mcp.json` or any generated setup artifact.
|
|
57
92
|
- Headless Jam access requires `native.jam.dev` for the installer and
|
|
58
93
|
`api.jam.dev` for CLI/API calls in any custom remote network allowlist.
|
|
@@ -70,6 +70,13 @@ read_notion_token() {
|
|
|
70
70
|
# Legacy fallback: the OS keychain written by the guided /lisa:setup:notion
|
|
71
71
|
# flow, for projects that have not adopted a credentials provider. Reached only
|
|
72
72
|
# when the chokepoint is absent or has no entry.
|
|
73
|
+
#
|
|
74
|
+
# This rung is REMOVED ON 2026-11-01 — a dated migration ramp, not a standing
|
|
75
|
+
# exemption (see credential-substrate-precedence, "Legacy OS-keychain fallback
|
|
76
|
+
# — removal date"). A keychain entry is machine-local ambient state no headless
|
|
77
|
+
# surface can reach, so a project resting on it has no working tier 1 in cron,
|
|
78
|
+
# CI, or a cloud session. Re-run /lisa:setup:notion before that date to store
|
|
79
|
+
# NOTION_API_TOKEN through the chokepoint instead.
|
|
73
80
|
case "$(uname -s)" in
|
|
74
81
|
Darwin) security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null ;;
|
|
75
82
|
Linux) command -v secret-tool >/dev/null && \
|
|
@@ -40,15 +40,38 @@ works interactively and headlessly — which is why it leads. The REST tier uses
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
POSTHOG_HOST=${POSTHOG_HOST:-https://app.posthog.com}
|
|
43
|
+
|
|
44
|
+
# Resolve the key through the chokepoint before giving up on the environment.
|
|
45
|
+
# `$POSTHOG_PERSONAL_API_KEY` is the documented fallback, not the only rung:
|
|
46
|
+
# without this, a project that keeps its credentials in Bitwarden, Doppler, or
|
|
47
|
+
# AWS has no tier 1 path at all and silently resolves through the interactive
|
|
48
|
+
# MCP — the exact divergence `credential-substrate-precedence` exists to remove.
|
|
49
|
+
read_posthog_key() {
|
|
50
|
+
[ -n "${POSTHOG_PERSONAL_API_KEY:-}" ] && { echo "$POSTHOG_PERSONAL_API_KEY"; return; }
|
|
51
|
+
local resolver
|
|
52
|
+
for resolver in .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs \
|
|
53
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs; do
|
|
54
|
+
if [ -f "$resolver" ]; then
|
|
55
|
+
local via_lisa
|
|
56
|
+
via_lisa=$(node "$resolver" get POSTHOG_PERSONAL_API_KEY 2>/dev/null) \
|
|
57
|
+
&& [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
|
|
58
|
+
break
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
return 1
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
posthog_api() {
|
|
44
65
|
local path="$1"
|
|
45
66
|
local method="${2:-GET}"
|
|
46
67
|
local body="${3:-}"
|
|
47
|
-
|
|
48
|
-
|
|
68
|
+
local key
|
|
69
|
+
key=$(read_posthog_key) || {
|
|
70
|
+
echo "Error: no PostHog key. Set POSTHOG_PERSONAL_API_KEY, or store it as" >&2
|
|
71
|
+
echo "POSTHOG_PERSONAL_API_KEY in this project's secrets provider." >&2
|
|
49
72
|
return 1
|
|
50
73
|
}
|
|
51
|
-
local args=(-sS -X "$method" -H "Authorization: Bearer $
|
|
74
|
+
local args=(-sS -X "$method" -H "Authorization: Bearer $key")
|
|
52
75
|
[ -n "$body" ] && args+=(-H "Content-Type: application/json" --data-binary "$body")
|
|
53
76
|
curl "${args[@]}" "${POSTHOG_HOST%/}/api${path}"
|
|
54
77
|
}
|
|
@@ -60,11 +83,25 @@ If neither tier works, fail with:
|
|
|
60
83
|
Error: no PostHog access substrate available. Authenticate the PostHog MCP or set POSTHOG_PERSONAL_API_KEY.
|
|
61
84
|
```
|
|
62
85
|
|
|
86
|
+
## Mutation boundary
|
|
87
|
+
|
|
88
|
+
Every operation in the Invocation Contract is **read-only** — analytics
|
|
89
|
+
retrieval. `query` is an HTTP POST, but it reads: it submits a query body and
|
|
90
|
+
changes no PostHog state. So the `credential-substrate-precedence` guarded
|
|
91
|
+
fallback for mutating operations (write, read back, assert the tenant from the
|
|
92
|
+
response, roll back on mismatch) is not engaged here, and a failed tier is
|
|
93
|
+
simply skipped. Adding a genuinely mutating operation — creating an insight,
|
|
94
|
+
editing a feature flag — pulls that protocol in: a write of unknown outcome MUST
|
|
95
|
+
reconcile by read-back before any retry.
|
|
96
|
+
|
|
63
97
|
## Invariants
|
|
64
98
|
|
|
65
99
|
- Tier order is `credential-substrate-precedence`: `POSTHOG_PERSONAL_API_KEY`
|
|
66
100
|
first, the PostHog MCP as a preserved first-class fallback. Identity-match
|
|
67
101
|
against the configured project is mandatory on every tier.
|
|
102
|
+
- The key is resolved through `lisa-secrets-access`, with the bare
|
|
103
|
+
`POSTHOG_PERSONAL_API_KEY` environment variable as the documented fallback.
|
|
104
|
+
Never read a second credential store directly.
|
|
68
105
|
- `POSTHOG_HOST` defaults to PostHog Cloud but can point at a self-hosted
|
|
69
106
|
deployment.
|
|
70
107
|
- Consumer skills do not embed PostHog REST paths.
|
|
@@ -39,28 +39,69 @@ Sentry documents API auth tokens for REST API calls, and the same token works
|
|
|
39
39
|
interactively and headlessly — which is why it leads. The REST tier uses:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
+
# Resolve the token through the chokepoint before giving up on the environment.
|
|
43
|
+
# `$SENTRY_AUTH_TOKEN` is the documented fallback, not the only rung: without
|
|
44
|
+
# this, a project that keeps its credentials in Bitwarden, Doppler, or AWS has
|
|
45
|
+
# no tier 1 path at all and silently resolves through the interactive MCP —
|
|
46
|
+
# the exact divergence `credential-substrate-precedence` exists to remove.
|
|
47
|
+
# Mirrors `linear-access`, `atlassian-access`, and `notion-access`.
|
|
48
|
+
read_sentry_token() {
|
|
49
|
+
[ -n "${SENTRY_AUTH_TOKEN:-}" ] && { echo "$SENTRY_AUTH_TOKEN"; return; }
|
|
50
|
+
local resolver
|
|
51
|
+
for resolver in .claude/skills/lisa-secrets-access/scripts/resolve-secret.mjs \
|
|
52
|
+
.agents/skills/lisa-secrets-access/scripts/resolve-secret.mjs; do
|
|
53
|
+
if [ -f "$resolver" ]; then
|
|
54
|
+
local via_lisa
|
|
55
|
+
via_lisa=$(node "$resolver" get SENTRY_AUTH_TOKEN 2>/dev/null) \
|
|
56
|
+
&& [ -n "$via_lisa" ] && { echo "$via_lisa"; return; }
|
|
57
|
+
break
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
sentry_api() {
|
|
43
64
|
local path="$1"
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
local token
|
|
66
|
+
token=$(read_sentry_token) || {
|
|
67
|
+
echo "Error: no Sentry auth token. Set SENTRY_AUTH_TOKEN, or store it as" >&2
|
|
68
|
+
echo "SENTRY_AUTH_TOKEN in this project's secrets provider." >&2
|
|
46
69
|
return 1
|
|
47
70
|
}
|
|
48
71
|
curl -sS "https://sentry.io/api/0${path}" \
|
|
49
|
-
-H "Authorization: Bearer $
|
|
72
|
+
-H "Authorization: Bearer $token"
|
|
50
73
|
}
|
|
51
74
|
```
|
|
52
75
|
|
|
76
|
+
Tier 1a authenticates `sentry-cli` from the **same resolved token**
|
|
77
|
+
(`SENTRY_AUTH_TOKEN=$(read_sentry_token) sentry-cli …`) — never from a second
|
|
78
|
+
credential store, and never from an interactive `sentry-cli login` keychain.
|
|
79
|
+
|
|
53
80
|
If neither tier works, fail with:
|
|
54
81
|
|
|
55
82
|
```text
|
|
56
83
|
Error: no Sentry access substrate available. Authenticate Sentry MCP/CLI or set SENTRY_AUTH_TOKEN.
|
|
57
84
|
```
|
|
58
85
|
|
|
86
|
+
## Mutation boundary
|
|
87
|
+
|
|
88
|
+
Every operation in the Invocation Contract is **read-only** — issue, event, and
|
|
89
|
+
release retrieval. So the `credential-substrate-precedence` guarded fallback for
|
|
90
|
+
mutating operations (write, read back, assert the tenant from the response, roll
|
|
91
|
+
back on mismatch) is not engaged here, and a failed tier is simply skipped. Any
|
|
92
|
+
future operation that mutates Sentry state — resolving an issue, editing a
|
|
93
|
+
release — is a write and MUST reconcile by read-back under that protocol before
|
|
94
|
+
any retry; do not add one to the contract without it.
|
|
95
|
+
|
|
59
96
|
## Invariants
|
|
60
97
|
|
|
61
98
|
- Tier order is `credential-substrate-precedence`: `SENTRY_AUTH_TOKEN` first, the
|
|
62
99
|
Sentry MCP as a preserved first-class fallback. Identity-match against the
|
|
63
100
|
configured org/project is mandatory on every tier.
|
|
101
|
+
- The token is resolved through `lisa-secrets-access`, with the bare
|
|
102
|
+
`SENTRY_AUTH_TOKEN` environment variable as the documented fallback. Never read
|
|
103
|
+
a second credential store (OS keychain, `~/.sentryclirc` token) directly — the
|
|
104
|
+
one-store rule lives at the chokepoint.
|
|
64
105
|
- Org/project come from `.sentryclirc`, `.lisa.config.json`, or explicit
|
|
65
106
|
operation args; never infer by searching all accessible orgs.
|
|
66
107
|
- Consumer skills do not embed Sentry REST paths.
|