Skip to content

Commit c2d401c

Browse files
author
Test
committed
Merge remote-tracking branch 'origin/main' into worktree-20260415-182323
2 parents 6098ae2 + cbc09e0 commit c2d401c

22 files changed

+1344
-28
lines changed

.claude/hooks/pre-commit/plugin-boundary-allowlist.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ plugins/dso/tests/**
2828
plugins/dso/docs/adapters/**
2929
plugins/dso/docs/contracts/**
3030
plugins/dso/docs/decisions/**
31+
plugins/dso/docs/designs/**
3132
plugins/dso/docs/templates/**
3233
plugins/dso/docs/workflows/**
3334
plugins/dso/docs/prompts/**

.test-index

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,15 +168,15 @@ plugins/dso/skills/onboarding/SKILL.md:tests/skills/test-onboarding-e2e.sh
168168
plugins/dso/skills/onboarding/SKILL.md:tests/skills/test-onboarding-ux-validation.sh
169169
plugins/dso/scripts/check-skill-refs.sh:tests/scripts/test-check-skill-refs.sh
170170
plugins/dso/scripts/scan-docs.sh:tests/skills/test-onboarding-scan-docs.sh
171-
docs/onboarding.md:tests/skills/test-nextjs-starter-docs.sh [test_onboarding_doc_exists]
171+
docs/onboarding.md:tests/skills/test-nextjs-starter-docs.sh
172172
plugins/dso/skills/onboarding/SKILL.md:tests/hooks/test-onboarding-batch-groups.sh
173173
plugins/dso/skills/onboarding/SKILL.md:tests/skills/test-onboarding-confidence-assignment.sh
174174
plugins/dso/skills/architect-foundation/SKILL.md:tests/skills/test-architect-foundation-skill.sh
175-
plugins/dso/skills/architect-foundation/SKILL.md:tests/skills/test-architect-foundation-skill.sh
176-
plugins/dso/skills/architect-foundation/SKILL.md:tests/skills/test-architect-foundation-skill.sh
177-
plugins/dso/skills/architect-foundation/SKILL.md:tests/skills/test-architect-foundation-skill.sh
178-
plugins/dso/skills/architect-foundation/SKILL.md:tests/skills/test-architect-foundation-skill.sh
179175
plugins/dso/skills/onboarding/SKILL.md:tests/skills/test-onboarding-skill.sh
180176
plugins/dso/skills/onboarding/SKILL.md:tests/skills/test-onboarding-e2e.sh
181-
plugins/dso/skills/onboarding/SKILL.md:tests/skills/test-onboarding-skill.sh
182177
plugins/dso/scripts/check-skill-refs.sh: tests/scripts/test-check-skill-refs.sh
178+
docs/onboarding.md:tests/skills/test-nextjs-starter-docs.sh
179+
plugins/dso/README.md:tests/skills/test-nextjs-starter-docs.sh
180+
CLAUDE.md:tests/skills/test-nextjs-starter-docs.sh
181+
plugins/dso/docs/designs/dso-nextjs-starter-plugin-install.md:tests/scripts/test-dso-nextjs-starter-plugin-install.sh [test_plugin_consent_doc_has_required_sections]
182+
plugins/dso/scripts/create-dso-app.sh:tests/scripts/test-create-dso-app.sh

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
| Review event stats | `.claude/scripts/dso review-stats.sh` |
4040
| Run a recipe transform | `.claude/scripts/dso recipe-executor.sh <recipe-name> [--param key=value ...]` |
4141
| Sync stale host-project artifacts to current plugin version | `.claude/scripts/dso update-artifacts` |
42+
| Bootstrap a NextJS project | `bash <(curl -fsSL https://raw.githubusercontent.com/<org>/digital-service-orchestra/HEAD/plugins/dso/scripts/create-dso-app.sh)` or `/dso:nextjs-starter` |
4243

4344
Less common: `check-skill-refs.sh`, `qualify-skill-refs.sh`.
4445

docs/onboarding.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# DSO Onboarding
2+
3+
## Bootstrapping a New NextJS Project
4+
5+
Use the DSO NextJS Starter to scaffold a fully-configured project with one command:
6+
7+
```bash
8+
bash <(curl -fsSL https://raw.githubusercontent.com/<org>/digital-service-orchestra/HEAD/plugins/dso/scripts/create-dso-app.sh)
9+
```
10+
11+
> **Note**: Replace `<org>` with your GitHub organization name once the template repository has been set up. See the DSO NextJS Starter README for more details.
12+
13+
## What the installer does
14+
15+
1. Checks and installs required dependencies (Homebrew, Node 20.x, pre-commit, Claude Code)
16+
2. Clones the DSO NextJS template repository
17+
3. Substitutes your project name throughout the template
18+
4. Installs npm dependencies
19+
5. Launches Claude Code with DSO pre-configured
20+
21+
## Prerequisites
22+
23+
- macOS with internet access
24+
- Homebrew will be installed if not present

plugins/dso/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dso",
3-
"version": "1.12.25",
3+
"version": "1.12.27",
44
"description": "Workflow infrastructure plugin for Claude Code projects",
55
"commands": "./commands/",
66
"skills": "./skills/",

plugins/dso/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# DSO (Digital Service Orchestrator) Plugin
2+
3+
DSO is a Claude Code plugin that provides AI-assisted development workflows for government digital services teams.
4+
5+
## DSO NextJS Starter
6+
7+
A one-command bootstrap for non-engineer NextJS prototyping. The nextjs-starter installer sets up a complete DSO-configured NextJS project with all required infrastructure files.
8+
9+
**Quick start**: See [`docs/onboarding.md`](../../docs/onboarding.md) for the full bootstrap command.
10+
11+
To install the nextjs-starter scaffolding, run the `create-dso-app.sh` installer script from the `scripts/` directory.
12+
13+
## Features
14+
15+
- Sprint orchestration and task management
16+
- TDD-driven implementation workflows
17+
- Code review and quality gates
18+
- Architecture enforcement
19+
20+
## Documentation
21+
22+
- **Installation**: See `docs/INSTALL.md`
23+
- **Configuration**: See `docs/CONFIGURATION-REFERENCE.md`
24+
- **Worktree Guide**: See `docs/WORKTREE-GUIDE.md`
25+
- **Ticket CLI Reference**: See `docs/ticket-cli-reference.md`
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# DSO NextJS Starter: Plugin Install Consent Design
2+
3+
## Overview
4+
5+
This document investigates whether placing `extraKnownMarketplaces` and `enabledPlugins` in a
6+
project's `.claude/settings.json` will trigger a consent/install dialog when a developer first
7+
opens the project in Claude Code — enabling the DSO plugin to be auto-installed without any
8+
manual `claude plugin install` step.
9+
10+
The use case is the **DSO NextJS Starter template**: a scaffolded project that ships with a
11+
pre-populated `.claude/settings.json` pointing to the DSO plugin marketplace. The question is
12+
whether a first-time user who clones the template and runs `claude` will be automatically
13+
prompted to install the DSO plugin.
14+
15+
---
16+
17+
## Research Findings
18+
19+
### Official Documentation (code.claude.com/docs/en/settings — April 2026)
20+
21+
The authoritative Claude Code settings reference describes `extraKnownMarketplaces` behavior
22+
under **Plugin settings**:
23+
24+
> **When a repository includes `extraKnownMarketplaces`**:
25+
> 1. Team members are prompted to install the marketplace when they trust the folder
26+
> 2. Team members are then prompted to install plugins from that marketplace
27+
> 3. Users can skip unwanted marketplaces or plugins (stored in user settings)
28+
> 4. Installation respects trust boundaries and requires explicit consent
29+
30+
This is a two-step flow: marketplace registration consent, then plugin install consent. Both are
31+
interactive prompts that appear at the workspace trust moment.
32+
33+
### GitHub Issue #13097: Interactive-Only Constraint
34+
35+
Issue [#13097](https://github.com/anthropics/claude-code/issues/13097) ("Clarify that
36+
extraKnownMarketplaces requires interactive trust dialog") documents that:
37+
38+
- The `extraKnownMarketplaces` feature **only activates during interactive mode**
39+
- In headless/print mode (`-p` flag), the trust dialog is skipped entirely and
40+
`extraKnownMarketplaces` is not processed
41+
- This is intentional: consent requires a human at the keyboard
42+
43+
An independent investigation (gist linked in the issue) confirmed:
44+
45+
> "Two separate systems exist: project-level `extraKnownMarketplaces` (ignored by plugin
46+
> commands) and user-level `known_marketplaces.json` (actually consulted). The auto-installation
47+
> bridge between them only engages during the interactive trust moment."
48+
49+
This means the mechanism works in the exact scenario we care about — a developer running
50+
`claude` interactively in a newly cloned repo — and does not apply to CI or headless runs.
51+
52+
### GitHub Issue #32607: Silent Failure for Uninstalled Enabled Plugins
53+
54+
Issue [#32607](https://github.com/anthropics/claude-code/issues/32607) ("No warning when
55+
enabledPlugins references a plugin that is not installed") identifies a current gap:
56+
57+
- If `enabledPlugins` lists a plugin but the user skips installation or the marketplace prompt
58+
doesn't fire, Claude Code **silently does nothing** — no error, no warning
59+
- Users see `Unknown skill: dso:sprint` with no diagnostic path
60+
61+
This does not affect the primary consent flow (which does present a prompt), but it matters
62+
for the degraded-path experience documented below.
63+
64+
### GitHub Issue #23737: Auto-Install Feature Request
65+
66+
Issue [#23737](https://github.com/anthropics/claude-code/issues/23737) ("Auto-install plugins
67+
from enabledPlugins in shared settings.json") was filed requesting fully automatic installation
68+
without a consent prompt. It was **closed as a duplicate** of an existing open feature request.
69+
70+
Key implication: as of April 2026, fully silent auto-install (no dialog at all) is **not
71+
implemented**. The consent prompt approach documented in the official docs is the current
72+
mechanism.
73+
74+
### Platform Consistency Note
75+
76+
Issue [#32268](https://github.com/anthropics/claude-code/issues/32268) documents that the
77+
official Anthropic marketplace (`claude-plugins-official`) is pre-registered on macOS but
78+
**not** on Windows. For a custom DSO marketplace, `extraKnownMarketplaces` registration is
79+
required on all platforms, making the `.claude/settings.json` approach consistently necessary
80+
regardless of OS.
81+
82+
---
83+
84+
## Success Path
85+
86+
**When `extraKnownMarketplaces` + `enabledPlugins` is used in project settings:**
87+
88+
A developer clones the DSO NextJS Starter and runs `claude` in interactive mode:
89+
90+
1. Claude Code detects a new (untrusted) project directory and presents the workspace trust dialog
91+
2. Developer selects "Yes, proceed" (trust the folder)
92+
3. Claude Code reads `.claude/settings.json`, finds `extraKnownMarketplaces` pointing to the
93+
DSO plugin marketplace
94+
4. A second prompt appears: "This project recommends the [DSO] marketplace — install it?"
95+
5. Developer confirms; the marketplace catalog is fetched
96+
6. A third prompt (or sequential continuation) appears for each plugin listed in
97+
`enabledPlugins`: "Install [digital-service-orchestra]?"
98+
7. Developer confirms; the DSO plugin installs and is available immediately
99+
100+
The user experience is a guided sequence of two or three consent dialogs, not a manual
101+
`claude plugin install` invocation. From the developer's perspective, opening a new project
102+
*just works* after clicking through the prompts.
103+
104+
**Limitations of this path:**
105+
106+
- Requires interactive mode — the prompts do not appear in `claude -p` (headless) runs
107+
- Users can skip each prompt (stored as "dismissed" in their local settings)
108+
- If skipped, the plugin is silently absent with no follow-up warning (see Issue #32607)
109+
- Does not apply in CI/CD pipelines — the template's README should document the manual
110+
`claude plugin marketplace add` + `claude plugin install` commands for CI contexts
111+
112+
---
113+
114+
## Failure Path
115+
116+
**Conditions under which the consent flow does not trigger:**
117+
118+
1. **Headless/CI mode** (`claude -p`): Trust dialog is skipped; `extraKnownMarketplaces` is
119+
never processed. This is intentional, not a bug.
120+
2. **Already-trusted directory**: If the developer has previously trusted the directory (e.g.,
121+
ran `claude` before the template's `.claude/settings.json` was committed), the trust dialog
122+
may not re-appear. The marketplace prompt fires only on the first trust event.
123+
3. **User dismisses the prompt**: Dismissed marketplaces and plugins are recorded in the user's
124+
`~/.claude/settings.json` and will not prompt again on subsequent runs.
125+
4. **Plugin listed in `enabledPlugins` is not yet installed**: If the marketplace prompt fires
126+
but the user skips the plugin install step, subsequent skill invocations fail silently
127+
(Issue #32607 — no warning is shown).
128+
129+
The failure path does not mean the configuration is wrong; it means the feature has clear
130+
interactive-only scope and a silent-degradation gap that operators should anticipate.
131+
132+
---
133+
134+
## Recommendation
135+
136+
This document declares the **Success Path** as **authoritative**.
137+
138+
The `extraKnownMarketplaces` field **does** trigger a consent/install prompt sequence when a
139+
developer opens a project for the first time in interactive Claude Code. This behavior is
140+
documented in the official settings reference and confirmed by community investigation.
141+
142+
**Template fork recommendation: include `.claude/settings.json` with these fields.**
143+
144+
The DSO NextJS Starter template should ship with a `.claude/settings.json` that declares:
145+
146+
```json
147+
{
148+
"extraKnownMarketplaces": {
149+
"digital-service-orchestra": {
150+
"source": {
151+
"source": "github",
152+
"repo": "navapbc/digital-service-orchestra"
153+
}
154+
}
155+
},
156+
"enabledPlugins": {
157+
"digital-service-orchestra@digital-service-orchestra": true
158+
}
159+
}
160+
```
161+
162+
This delivers the onboarding goal: first-time users are guided through plugin installation
163+
without needing to know about the installer script `create-dso-app.sh` or the plugin system
164+
internals.
165+
166+
**Additional steps for a complete experience:**
167+
168+
1. **README callout**: Document that Claude Code will prompt to install the DSO marketplace on
169+
first launch; explain what to do if the prompt was skipped (run
170+
`/plugin marketplace add navapbc/digital-service-orchestra` then
171+
`/plugin install digital-service-orchestra@digital-service-orchestra`)
172+
2. **CI guidance**: For teams running `claude -p` in CI, document the explicit install commands
173+
since the consent flow does not run in headless mode
174+
3. **Trust re-trigger caveat**: If a developer has run `claude` in the project directory
175+
before the settings file was added, they may need to run the manual install; the trust event
176+
only fires once per directory
177+
178+
The recommendation does **not** depend on the silent auto-install feature requested in
179+
Issue #23737 (not yet implemented). The consent-dialog path is sufficient for interactive
180+
developer onboarding.

plugins/dso/hooks/record-test-status.sh

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ count_centrality() {
226226

227227
# Escape regex metacharacters in module_name to prevent injection (Bug 5 fix).
228228
local escaped_module_name
229+
# shellcheck disable=SC2016 # single quotes are intentional: \& is sed syntax, not shell expansion
229230
escaped_module_name=$(printf '%s' "$module_name" | sed 's/[.[\*^$()+?{|\\]/\\&/g')
230231

231232
# Hardcoded default patterns (fallback when no config patterns are present).
@@ -655,10 +656,21 @@ DIFF_HASH=$("$HOOK_DIR/compute-diff-hash.sh")
655656
# Uses grep-based fan-in counting (no external tools required).
656657
# When ast-grep (sg) is not installed, emits a diagnostic note but still
657658
# performs centrality scoring via grep (the primary counting method).
659+
660+
# _is_astgrep_sg: returns 0 only when the sg binary in PATH is ast-grep.
661+
# Ubuntu ships shadow-utils' sg (switch-group) at /usr/bin/sg; that binary
662+
# also satisfies `command -v sg` but is not ast-grep. We verify by checking
663+
# that `sg --version` produces a version line starting with "sg <digit>",
664+
# which ast-grep does and shadow-utils' sg does not.
665+
_is_astgrep_sg() {
666+
command -v sg >/dev/null 2>&1 || return 1
667+
sg --version 2>/dev/null | grep -qE '^(sg|ast-grep) [0-9]' || return 1
668+
}
669+
658670
FULL_SUITE=false
659671
_max_centrality=0
660672
_CENTRALITY_LOG="$ARTIFACTS_DIR/centrality-log.jsonl"
661-
if ! command -v sg >/dev/null 2>&1; then
673+
if ! _is_astgrep_sg; then
662674
echo "NOTE: ast-grep (sg) not installed — centrality scoring uses grep-based fan-in counting" >&2
663675
fi
664676

@@ -727,7 +739,7 @@ else
727739
_ts_log=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "unknown")
728740
if [[ "$_centrality" -gt "$_CENTRALITY_THRESHOLD" ]] 2>/dev/null; then
729741
_decision="full_suite"
730-
elif ! command -v sg >/dev/null 2>&1; then
742+
elif ! _is_astgrep_sg; then
731743
_decision="skipped_no_sg"
732744
else
733745
_decision="associated_only"
@@ -811,6 +823,7 @@ FAILED_TESTS_LIST=""
811823
# Write "partial" (not STATUS) to test-gate-status so the pre-commit test
812824
# gate never accepts a mid-run snapshot as a valid pass — STATUS may still
813825
# be "passed" while untested files remain in the queue.
826+
# shellcheck disable=SC2329 # invoked indirectly via: trap '_write_partial_status' URG
814827
_write_partial_status() {
815828
local _ts
816829
_ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
@@ -853,7 +866,7 @@ if [[ "$FULL_SUITE" == true ]]; then
853866
while IFS= read -r _tf; do
854867
[[ -z "$_tf" ]] && continue
855868
_discovered_test_files+=("$_tf")
856-
_rel="${_tf#$REPO_ROOT/}"
869+
_rel="${_tf#"$REPO_ROOT"/}"
857870
if [[ -n "$_all_test_files" ]]; then
858871
_all_test_files="${_all_test_files},${_rel}"
859872
else
@@ -964,6 +977,7 @@ if ms_is_merge_in_progress || ms_is_rebase_in_progress; then
964977
_rts_isolation_dir=$(mktemp -d /tmp/rts-git-isolation-XXXXXX)
965978
git init -q "$_rts_isolation_dir" 2>/dev/null
966979
export _MERGE_STATE_GIT_DIR="$_rts_isolation_dir/.git"
980+
# shellcheck disable=SC2329 # invoked indirectly via: trap '_rts_cleanup_isolation' EXIT
967981
_rts_cleanup_isolation() { rm -rf "$_rts_isolation_dir" 2>/dev/null; }
968982
trap '_rts_cleanup_isolation' EXIT
969983
fi

plugins/dso/scripts/bridge-outbound.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,25 @@ def detect_status_flap(
235235
if not all_status_events:
236236
return False
237237

238-
# Filter to events within window_seconds of the most recent event
239-
max_ts = max(ts for ts, _ in all_status_events)
240-
cutoff = max_ts - window_seconds
241-
status_events = [(ts, s) for ts, s in all_status_events if ts >= cutoff]
238+
# Normalize all timestamps to nanoseconds before comparison so that
239+
# mixed-precision events (seconds ~1.7e9 from old code, nanoseconds ~1.78e18
240+
# from new code) can be compared correctly during the migration window.
241+
# A threshold of 1e12 safely distinguishes nanoseconds from seconds because
242+
# the Unix epoch in seconds is ~1.7e9 (well below 1e12) while in nanoseconds
243+
# it is ~1.78e18 (well above 1e12).
244+
_NS_THRESHOLD = 1_000_000_000_000 # 1e12: values above this are nanoseconds
245+
_NS_PER_SEC = 1_000_000_000
246+
247+
def _to_ns(ts: int) -> int:
248+
"""Normalize a timestamp to nanoseconds regardless of original precision."""
249+
return ts if ts > _NS_THRESHOLD else ts * _NS_PER_SEC
250+
251+
# Normalize all events to nanoseconds so cutoff math is consistent
252+
all_status_events_ns = [(_to_ns(ts), s) for ts, s in all_status_events]
253+
max_ts_ns = max(ts for ts, _ in all_status_events_ns)
254+
window_ns = window_seconds * _NS_PER_SEC
255+
cutoff = max_ts_ns - window_ns
256+
status_events = [(ts, s) for ts, s in all_status_events_ns if ts >= cutoff]
242257

243258
# Sort by timestamp
244259
status_events.sort(key=lambda x: x[0])

0 commit comments

Comments
 (0)