@windyroad/itil 0.20.0-preview.210 → 0.21.0-preview.212

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "wr-itil",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "ITIL-aligned IT service management for Claude Code"
5
5
  }
package/hooks/hooks.json CHANGED
@@ -15,6 +15,10 @@
15
15
  {
16
16
  "matcher": "Bash",
17
17
  "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/p057-staging-trap-detect.sh" }]
18
+ },
19
+ {
20
+ "matcher": "Bash",
21
+ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/pre-publish-intake-gate.sh" }]
18
22
  }
19
23
  ],
20
24
  "Stop": [
@@ -0,0 +1,130 @@
1
+ #!/bin/bash
2
+ # PreToolUse:Bash hook (P065 / ADR-036 Trigger 2): blocks `npm publish`
3
+ # and `gh pr merge` of a `changeset-release/*` PR when the project is
4
+ # missing one or more of the five intake files.
5
+ #
6
+ # Required intake files (per ADR-036 Detection step 5):
7
+ # .github/ISSUE_TEMPLATE/config.yml
8
+ # .github/ISSUE_TEMPLATE/problem-report.yml
9
+ # SECURITY.md
10
+ # SUPPORT.md
11
+ # CONTRIBUTING.md
12
+ #
13
+ # Bypass / opt-out paths (in priority order, per architect direction):
14
+ # 1. INTAKE_BYPASS=1 in env -> short-circuit BEFORE existence check
15
+ # (consistent with RISK_BYPASS naming
16
+ # convention in external-comms-gate.sh)
17
+ # 2. .claude/.intake-scaffold-declined marker present -> permit
18
+ # (ADR-009 marker semantics; explicit
19
+ # decline by adopter, persistent until
20
+ # file deleted)
21
+ # 3. All five intake files present -> permit (idempotent default)
22
+ # 4. Otherwise -> deny + delegate to /wr-itil:scaffold-intake
23
+ #
24
+ # This hook composes with risk-scorer's git-push-gate.sh: both fire on
25
+ # Bash with `gh pr merge` matchers, and either may deny independently.
26
+ # In practice git-push-gate denies all `gh pr merge` and routes to
27
+ # `npm run release:watch` which subsequently runs `npm publish` — at
28
+ # which point this hook fires.
29
+
30
+ set -euo pipefail
31
+
32
+ # ---------- 1. Bypass: check INTAKE_BYPASS BEFORE anything else ----------
33
+ if [ "${INTAKE_BYPASS:-0}" = "1" ]; then
34
+ exit 0
35
+ fi
36
+
37
+ INPUT=$(cat)
38
+
39
+ TOOL_NAME=$(printf '%s' "$INPUT" | python3 -c "
40
+ import sys, json
41
+ try:
42
+ print(json.load(sys.stdin).get('tool_name', ''))
43
+ except Exception:
44
+ print('')
45
+ " 2>/dev/null || echo "")
46
+
47
+ # Out of scope: only Bash invocations are gated.
48
+ [ "$TOOL_NAME" = "Bash" ] || exit 0
49
+
50
+ COMMAND=$(printf '%s' "$INPUT" | python3 -c "
51
+ import sys, json
52
+ try:
53
+ print(json.load(sys.stdin).get('tool_input', {}).get('command', ''))
54
+ except Exception:
55
+ print('')
56
+ " 2>/dev/null || echo "")
57
+
58
+ # ---------- Surface detection ----------
59
+ IN_SCOPE=0
60
+
61
+ # Match `npm publish` (with or without flags).
62
+ if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*npm publish(\s|$)'; then
63
+ IN_SCOPE=1
64
+ fi
65
+
66
+ # Match `gh pr merge` against a changeset-release/* PR. The changesets
67
+ # release-PR pattern is the only `gh pr merge` shape that flips the
68
+ # publish boundary. A regular feature-branch merge does not.
69
+ if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*gh pr merge\b' \
70
+ && echo "$COMMAND" | grep -qE 'changeset-release/'; then
71
+ IN_SCOPE=1
72
+ fi
73
+
74
+ [ "$IN_SCOPE" = "1" ] || exit 0
75
+
76
+ # ---------- 2. Decline marker (ADR-009 persistent marker) ----------
77
+ if [ -f ".claude/.intake-scaffold-declined" ]; then
78
+ exit 0
79
+ fi
80
+
81
+ # ---------- 3. Intake-file existence check ----------
82
+ MISSING=()
83
+ for path in \
84
+ ".github/ISSUE_TEMPLATE/config.yml" \
85
+ ".github/ISSUE_TEMPLATE/problem-report.yml" \
86
+ "SECURITY.md" \
87
+ "SUPPORT.md" \
88
+ "CONTRIBUTING.md"; do
89
+ if [ ! -f "$path" ]; then
90
+ MISSING+=("$path")
91
+ fi
92
+ done
93
+
94
+ if [ "${#MISSING[@]}" -eq 0 ]; then
95
+ exit 0
96
+ fi
97
+
98
+ # ---------- 4. Deny + delegate ----------
99
+ deny_reason() {
100
+ local missing_list
101
+ missing_list=$(printf ' - %s\n' "${MISSING[@]}")
102
+ cat <<EOF
103
+ BLOCKED (P065 / ADR-036 pre-publish intake gate): ${#MISSING[@]} of 5 intake files missing — downstream reporters would hit a blank issue form and have no declared security-disclosure channel.
104
+
105
+ Missing:
106
+ ${missing_list}
107
+
108
+ Recovery, in priority order:
109
+ 1. Run /wr-itil:scaffold-intake to scaffold the missing intake files (recommended for first-time adopters).
110
+ 2. Set INTAKE_BYPASS=1 to override for documented exceptions:
111
+ INTAKE_BYPASS=1 npm publish
112
+ 3. Decline scaffolding entirely (suppresses the gate indefinitely):
113
+ mkdir -p .claude && touch .claude/.intake-scaffold-declined
114
+ EOF
115
+ }
116
+
117
+ REASON=$(deny_reason)
118
+
119
+ python3 -c "
120
+ import json, sys
121
+ print(json.dumps({
122
+ 'hookSpecificOutput': {
123
+ 'hookEventName': 'PreToolUse',
124
+ 'permissionDecision': 'deny',
125
+ 'permissionDecisionReason': sys.argv[1]
126
+ }
127
+ }))
128
+ " "$REASON"
129
+
130
+ exit 0
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # P065 / ADR-036: pre-publish-intake-gate.sh PreToolUse:Bash hook must deny
4
+ # `npm publish` (and changesets-release `gh pr merge`) when the four intake
5
+ # files are missing, unless the project has opted out via the decline
6
+ # marker (`.claude/.intake-scaffold-declined`) or the user sets the
7
+ # `INTAKE_BYPASS=1` env override.
8
+ #
9
+ # Required intake files (per ADR-036 Detection step 5):
10
+ # .github/ISSUE_TEMPLATE/config.yml
11
+ # .github/ISSUE_TEMPLATE/problem-report.yml
12
+ # SECURITY.md
13
+ # SUPPORT.md
14
+ # CONTRIBUTING.md
15
+ #
16
+ # Per feedback_behavioural_tests.md (P081): behavioural assertions —
17
+ # simulate the hook's payload on stdin and assert on emitted JSON
18
+ # permissionDecision and exit status. No source-grep on hook content.
19
+
20
+ setup() {
21
+ SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
22
+ HOOK="$SCRIPT_DIR/pre-publish-intake-gate.sh"
23
+ ORIG_DIR="$PWD"
24
+ TEST_DIR=$(mktemp -d)
25
+ cd "$TEST_DIR"
26
+ unset INTAKE_BYPASS
27
+ }
28
+
29
+ teardown() {
30
+ cd "$ORIG_DIR"
31
+ rm -rf "$TEST_DIR"
32
+ unset INTAKE_BYPASS
33
+ }
34
+
35
+ # Helper: simulate the PreToolUse:Bash payload on stdin.
36
+ run_bash_hook() {
37
+ local cmd="$1"
38
+ local json
39
+ json=$(printf '{"tool_name":"Bash","tool_input":{"command":"%s"}}' "$cmd")
40
+ echo "$json" | bash "$HOOK"
41
+ }
42
+
43
+ # Helper: scaffold all five intake files in the test directory.
44
+ scaffold_all_intake() {
45
+ mkdir -p .github/ISSUE_TEMPLATE
46
+ echo "blank_issues_enabled: false" > .github/ISSUE_TEMPLATE/config.yml
47
+ echo "name: Report a problem" > .github/ISSUE_TEMPLATE/problem-report.yml
48
+ echo "# Security Policy" > SECURITY.md
49
+ echo "# Getting Help" > SUPPORT.md
50
+ echo "# Contributing" > CONTRIBUTING.md
51
+ }
52
+
53
+ # Helper: set the decline marker.
54
+ decline() {
55
+ mkdir -p .claude
56
+ : > .claude/.intake-scaffold-declined
57
+ }
58
+
59
+ # --- Allow paths ---
60
+
61
+ @test "allow: npm publish proceeds when all five intake files are present" {
62
+ scaffold_all_intake
63
+ run run_bash_hook "npm publish"
64
+ [ "$status" -eq 0 ]
65
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
66
+ }
67
+
68
+ @test "allow: missing intake but decline marker present permits publish" {
69
+ decline
70
+ run run_bash_hook "npm publish"
71
+ [ "$status" -eq 0 ]
72
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
73
+ }
74
+
75
+ @test "allow: missing intake but INTAKE_BYPASS=1 permits publish (checked BEFORE existence)" {
76
+ # Architect direction: INTAKE_BYPASS must short-circuit before the
77
+ # existence check fires.
78
+ INTAKE_BYPASS=1 run run_bash_hook "npm publish"
79
+ [ "$status" -eq 0 ]
80
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
81
+ }
82
+
83
+ @test "allow: non-publish bash commands are out of scope" {
84
+ run run_bash_hook "ls -la"
85
+ [ "$status" -eq 0 ]
86
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
87
+ run run_bash_hook "git status"
88
+ [ "$status" -eq 0 ]
89
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
90
+ }
91
+
92
+ @test "allow: non-Bash tool calls are out of scope" {
93
+ json='{"tool_name":"Read","tool_input":{"file_path":"foo"}}'
94
+ run bash -c "echo '$json' | bash $HOOK"
95
+ [ "$status" -eq 0 ]
96
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
97
+ }
98
+
99
+ # --- Deny paths ---
100
+
101
+ @test "deny: npm publish with no intake files, no marker, no bypass" {
102
+ run run_bash_hook "npm publish"
103
+ [ "$status" -eq 0 ]
104
+ [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
105
+ [[ "$output" == *"intake"* ]]
106
+ [[ "$output" == *"scaffold-intake"* ]]
107
+ }
108
+
109
+ @test "deny: npm publish with partial intake (3 of 5) still denies" {
110
+ mkdir -p .github/ISSUE_TEMPLATE
111
+ echo "blank_issues_enabled: false" > .github/ISSUE_TEMPLATE/config.yml
112
+ echo "name: Report a problem" > .github/ISSUE_TEMPLATE/problem-report.yml
113
+ echo "# Security Policy" > SECURITY.md
114
+ # SUPPORT.md and CONTRIBUTING.md missing
115
+ run run_bash_hook "npm publish"
116
+ [ "$status" -eq 0 ]
117
+ [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
118
+ }
119
+
120
+ @test "deny message names the recovery affordances (skill, marker, bypass)" {
121
+ run run_bash_hook "npm publish"
122
+ [ "$status" -eq 0 ]
123
+ [[ "$output" == *"scaffold-intake"* ]]
124
+ [[ "$output" == *"INTAKE_BYPASS"* ]]
125
+ [[ "$output" == *".intake-scaffold-declined"* ]]
126
+ }
127
+
128
+ # --- gh pr merge on changeset-release/* ---
129
+
130
+ @test "deny: gh pr merge of a changeset-release/* PR with missing intake" {
131
+ # ADR-036 Trigger 2 also matches gh pr merge against changeset-release/*
132
+ # branches (the changesets release-PR pattern).
133
+ run run_bash_hook "gh pr merge 42 --repo windyroad/example --branch changeset-release/main"
134
+ [ "$status" -eq 0 ]
135
+ [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
136
+ }
137
+
138
+ @test "allow: gh pr merge of a non-changeset PR is out of scope" {
139
+ # Only the changeset-release/* branch pattern is in scope; a regular
140
+ # feature-branch merge does not flip the publish boundary.
141
+ run run_bash_hook "gh pr merge 99 --branch feature/foo"
142
+ [ "$status" -eq 0 ]
143
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.20.0-preview.210",
3
+ "version": "0.21.0-preview.212",
4
4
  "description": "ITIL-aligned IT service management for Claude Code (problem, and future incident/change skills)",
5
5
  "bin": {
6
6
  "windyroad-itil": "./bin/install.mjs"
@@ -13,6 +13,25 @@ Create, update, or transition problem tickets following an ITIL-aligned problem
13
13
 
14
14
  When referencing problem IDs, ADR IDs, or JTBD IDs in prose output, always include the human-readable title on first mention. Use the format `P029 (Edit gate overhead for governance docs)`, not bare `P029`. Tables with separate ID and Title columns are fine as-is.
15
15
 
16
+ ## First-run intake-scaffold pointer (P065 / ADR-036)
17
+
18
+ This skill is one of the two host skills wired to surface the [`/wr-itil:scaffold-intake`](../scaffold-intake/SKILL.md) skill on first invocation in a project that has not yet adopted the OSS intake surface. The contract is documented in [ADR-036](../../../../docs/decisions/036-scaffold-downstream-oss-intake.proposed.md) (Scaffold downstream OSS intake — skill + layered triggers).
19
+
20
+ **Preamble check** (run before Step 0 of any operation):
21
+
22
+ 1. Look for the four intake paths: `.github/ISSUE_TEMPLATE/config.yml`, `.github/ISSUE_TEMPLATE/problem-report.yml`, `SECURITY.md`, `SUPPORT.md`, `CONTRIBUTING.md`.
23
+ 2. Look for `.claude/.intake-scaffold-declined` (explicit decline marker — never re-prompt).
24
+ 3. Look for `.claude/.intake-scaffold-done` (done marker — already scaffolded).
25
+
26
+ If any intake file is missing AND both markers are absent, surface the scaffold-intake skill:
27
+
28
+ | Mode | Behaviour |
29
+ |---|---|
30
+ | **Foreground (interactive)** | Fire one-shot `AskUserQuestion` per ADR-013 Rule 1: header `"Scaffold OSS intake?"`, three options — **Scaffold now** (delegate to `/wr-itil:scaffold-intake`), **Not now (ask again next session)** (no marker; re-prompt next time), **Decline (never prompt in this project)** (write `.claude/.intake-scaffold-declined`). |
31
+ | **AFK orchestrator (Rule 6 fail-safe)** | Do **not** fire `AskUserQuestion`. Append a one-line `"pending intake scaffold"` note to the iteration's `ITERATION_SUMMARY` notes field. Do **not** auto-scaffold — JTBD-006 forbids the agent from making this judgement call. The user catches up on next interactive session. |
32
+
33
+ The preamble check is a one-shot; the `.intake-scaffold-done` and `.intake-scaffold-declined` markers (ADR-009 persistent-marker semantics) suppress re-prompts in subsequent sessions without TTL expiry.
34
+
16
35
  ## Operations
17
36
 
18
37
  - **Create**: `problem <title or description>` — creates a new open problem
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # P065 / ADR-036 Confirmation line 198: manage-problem SKILL.md must
4
+ # wire the first-run intake-scaffold prompt — citing ADR-036 + the AFK
5
+ # fail-safe + the marker contract.
6
+ #
7
+ # Doc-lint structural test (Permitted Exception per ADR-005). The wiring
8
+ # itself is a SKILL.md preamble pointer (architect direction 2026-04-26),
9
+ # so behavioural assertions live at the contract layer of scaffold-intake;
10
+ # this bats fixes the wiring point so future maintainers cannot silently
11
+ # remove the cross-reference.
12
+
13
+ setup() {
14
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
15
+ SKILL_MD="$REPO_ROOT/packages/itil/skills/manage-problem/SKILL.md"
16
+ }
17
+
18
+ @test "manage-problem: SKILL.md cites ADR-036 (first-run-prompt contract)" {
19
+ run grep -F 'ADR-036' "$SKILL_MD"
20
+ [ "$status" -eq 0 ]
21
+ }
22
+
23
+ @test "manage-problem: SKILL.md cross-references the scaffold-intake skill" {
24
+ run grep -F 'wr-itil:scaffold-intake' "$SKILL_MD"
25
+ [ "$status" -eq 0 ]
26
+ }
27
+
28
+ @test "manage-problem: SKILL.md documents the first-run intake-scaffold detection clause" {
29
+ # Detection clause must reference at least one intake-file path AND the
30
+ # decline marker so future maintainers see how the trigger fires.
31
+ run grep -iE 'intake' "$SKILL_MD"
32
+ [ "$status" -eq 0 ]
33
+ run grep -F '.claude/.intake-scaffold-declined' "$SKILL_MD"
34
+ [ "$status" -eq 0 ]
35
+ }
36
+
37
+ @test "manage-problem: SKILL.md documents the AFK fail-safe (no AskUserQuestion in AFK; iteration-report note)" {
38
+ # Per ADR-013 Rule 6, the AFK orchestrator branch must not fire
39
+ # AskUserQuestion. The SKILL.md preamble pointer must call out this
40
+ # fail-safe so adopters reading the skill understand the divergence.
41
+ run grep -iE 'afk|rule 6' "$SKILL_MD"
42
+ [ "$status" -eq 0 ]
43
+ }
@@ -36,6 +36,12 @@ This skill **does NOT** implement ADR-027's Step-0 auto-delegation pattern. Per
36
36
 
37
37
  **Trigger to revisit**: if a second skill that reads upstream repo content lands (per ADR-024's Reassessment Criteria), reconsider whether the cross-cutting "read upstream" pattern should move into a Step-0-delegated subagent that this skill calls.
38
38
 
39
+ ## Reciprocal sibling: scaffold-intake (P065 / ADR-036)
40
+
41
+ This skill files reports against an upstream's intake surface. Its reciprocal — [`/wr-itil:scaffold-intake`](../scaffold-intake/SKILL.md) — scaffolds the intake surface on a downstream project so that downstream can in turn be a target of further `report-upstream` calls. Together they close the ecosystem-level intake-shape discipline P055 partial coverage left open. Adopters who run this skill against a target without an `.github/ISSUE_TEMPLATE/` directory should be encouraged to suggest the target adopt `/wr-itil:scaffold-intake` to gain the full problem-first intake set this skill prefers (per ADR-033 Step 3 classifier).
42
+
43
+ [ADR-036](../../../../docs/decisions/036-scaffold-downstream-oss-intake.proposed.md) is the design record for the reciprocal-scaffolding side; it establishes the layered triggers (first-run prompt + pre-publish gate + optional CI check) and the marker-suppressed lifecycle.
44
+
39
45
  ## Voice-tone gate interaction (ADR-028)
40
46
 
41
47
  The skill's `gh issue create` (Step 5) and `gh api repos/.../security-advisories` (Step 6) calls are **on the gated surface list per [ADR-028](../../../docs/decisions/028-voice-tone-gate-external-comms.proposed.md)** (Voice-tone gate on external communications). Expected behaviour during these tool calls:
@@ -0,0 +1,196 @@
1
+ ---
2
+ name: wr-itil:scaffold-intake
3
+ description: Scaffold the four OSS intake surfaces (.github/ISSUE_TEMPLATE/, SECURITY.md, SUPPORT.md, CONTRIBUTING.md) for a downstream project that adopts @windyroad/itil. Idempotent, foreground-synchronous, and respects ADR-009 marker semantics. Implements the contract in ADR-036.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
5
+ ---
6
+
7
+ # Scaffold Intake — Downstream OSS Intake Skill
8
+
9
+ Scaffold the five intake files every project in the Windy Road ecosystem needs to receive structured problem reports and route security disclosure properly:
10
+
11
+ - `.github/ISSUE_TEMPLATE/config.yml`
12
+ - `.github/ISSUE_TEMPLATE/problem-report.yml`
13
+ - `SECURITY.md`
14
+ - `SUPPORT.md`
15
+ - `CONTRIBUTING.md`
16
+
17
+ Templates are seeded from this repo's P066-corrected problem-first intake set and are substituted with per-project values for project name, repository URL, plugin list, and security-contact path.
18
+
19
+ This skill implements the contract in [ADR-036](../../../../docs/decisions/036-scaffold-downstream-oss-intake.proposed.md) (Scaffold downstream OSS intake — skill + layered triggers). It is the reciprocal of [`/wr-itil:report-upstream`](../report-upstream/SKILL.md) — that skill files reports against upstream intake; this skill creates the intake surface so a downstream project can be a target.
20
+
21
+ ## Pattern
22
+
23
+ This skill is **foreground-synchronous** per [ADR-032](../../../../docs/decisions/032-governance-skill-invocation-patterns.proposed.md) (Governance skill invocation patterns). Scaffolding writes files into the project that the user normally wants to review before they commit; the wrapped-pattern subagent shape would defeat that review. The skill commits its own work per [ADR-014](../../../../docs/decisions/014-governance-skills-commit-their-own-work.proposed.md).
24
+
25
+ ## Invocation
26
+
27
+ ```
28
+ /wr-itil:scaffold-intake [--dry-run] [--force] [--project-name <name>] [--project-url <url>] [--security-contact <path>] [--ci]
29
+ ```
30
+
31
+ | Flag | Effect |
32
+ |---|---|
33
+ | `--dry-run` | Preview the files that would be scaffolded; no writes. |
34
+ | `--force` | Overwrite present files after a diff-and-replace prompt. Off by default — present files are reported as "already present" and skipped. |
35
+ | `--project-name <name>` | Override auto-detected project name (default: `package.json` `name`, fallback to repo dirname). |
36
+ | `--project-url <url>` | Override auto-detected repository URL (default: `package.json` `repository.url`, fallback to `git remote get-url origin`). |
37
+ | `--security-contact <path>` | Override the security-disclosure path written into SECURITY.md (default: `Use GitHub Security Advisories`). |
38
+ | `--ci` | Also emit `.github/workflows/intake-check.yml` (Trigger 3, optional). |
39
+
40
+ ## Steps
41
+
42
+ ### 1. Detect project metadata
43
+
44
+ ```bash
45
+ PROJECT_NAME=$(node -p "require('./package.json').name" 2>/dev/null || basename "$PWD")
46
+ PROJECT_URL=$(node -p "require('./package.json').repository?.url || ''" 2>/dev/null \
47
+ | sed -E 's|^git\+||; s|\.git$||' \
48
+ || git remote get-url origin 2>/dev/null \
49
+ || echo "")
50
+ YEAR=$(date +%Y)
51
+ ```
52
+
53
+ `--project-name` and `--project-url` flags override the auto-detected values when supplied. `package.json` shapes vary across adopters; if detection produces empty values, surface a clear error suggesting the override flags rather than substituting an empty token.
54
+
55
+ Plugin list: enumerate installed `@windyroad/*` packages from `.claude-plugin/plugin.json` (if present) or from `package.json` dev-dependencies. Used for the SUPPORT.md "affected plugin or component" enumeration.
56
+
57
+ ### 2. Enumerate target paths
58
+
59
+ Required intake files (per ADR-036 Detection step 5):
60
+
61
+ ```
62
+ .github/ISSUE_TEMPLATE/config.yml
63
+ .github/ISSUE_TEMPLATE/problem-report.yml
64
+ SECURITY.md
65
+ SUPPORT.md
66
+ CONTRIBUTING.md
67
+ ```
68
+
69
+ For each path: classify as **missing**, **present-and-current** (template-substituted output matches existing file content), or **present-and-outdated** (existing differs from substituted template).
70
+
71
+ ### 3. AskUserQuestion: which files to scaffold (foreground only)
72
+
73
+ In foreground (interactive) mode, fire `AskUserQuestion` per ADR-013 Rule 1 with options scoped to the missing set:
74
+
75
+ - **Scaffold all missing** — write every absent template, skip present files.
76
+ - **Scaffold with review** — preview each substituted template before writing.
77
+ - **Dry-run** — preview only, no writes.
78
+ - **Cancel** — exit without action.
79
+
80
+ If `--force` is set AND outdated-present files exist, the prompt offers a fifth option for diff-and-replace per file. Architect direction (2026-04-26): keep the prompt one-shot when missing list is small; per-file prompts only when the user explicitly opts into review.
81
+
82
+ ### 4. Substitute templates and write files
83
+
84
+ Templates live in `templates/` adjacent to this SKILL.md. Substitution is mustache-style — no runtime dependency:
85
+
86
+ | Token | Value source |
87
+ |---|---|
88
+ | `{{project_name}}` | Detected or `--project-name` flag. |
89
+ | `{{project_url}}` | Detected or `--project-url` flag. |
90
+ | `{{plugin_list}}` | Comma-separated installed `@windyroad/*` plugins. |
91
+ | `{{security_contact}}` | Detected from existing SECURITY.md (when only that file is present); else `--security-contact` flag; fallback `Use GitHub Security Advisories`. |
92
+ | `{{year}}` | `date +%Y` at scaffold time. |
93
+
94
+ `sed` substitution sketch (idempotent, no runtime tooling):
95
+
96
+ ```bash
97
+ sed \
98
+ -e "s|{{project_name}}|$PROJECT_NAME|g" \
99
+ -e "s|{{project_url}}|$PROJECT_URL|g" \
100
+ -e "s|{{plugin_list}}|$PLUGIN_LIST|g" \
101
+ -e "s|{{security_contact}}|$SECURITY_CONTACT|g" \
102
+ -e "s|{{year}}|$YEAR|g" \
103
+ templates/SECURITY.md.tmpl > SECURITY.md
104
+ ```
105
+
106
+ For each missing file: substitute, write to the target path, report the write + a short diff to stdout.
107
+
108
+ For each present-and-current file: report "already present — skipped" without modification.
109
+
110
+ For each present-and-outdated file: when `--force` is set, offer diff-and-replace; otherwise skip with a clear note that `--force` would update it.
111
+
112
+ ### 5. Mark scaffold as done
113
+
114
+ After successful write:
115
+
116
+ ```bash
117
+ mkdir -p .claude
118
+ : > .claude/.intake-scaffold-done
119
+ ```
120
+
121
+ The marker suppresses Trigger-1 first-run prompts in subsequent `/wr-itil:manage-problem` and `/wr-itil:work-problems` invocations. ADR-009 marker semantics — persistent (no TTL), file-presence is the policy signal, deletable to reset.
122
+
123
+ ### 6. Commit per ADR-014
124
+
125
+ ```bash
126
+ git add .github/ISSUE_TEMPLATE/ SECURITY.md SUPPORT.md CONTRIBUTING.md .claude/.intake-scaffold-done
127
+ git -c commit.gpgsign=false commit -m "docs: scaffold OSS intake (ISSUE_TEMPLATE, SECURITY, SUPPORT, CONTRIBUTING)"
128
+ ```
129
+
130
+ Follow the project's existing commit-gate flow: the commit triggers `wr-risk-scorer:pipeline` per ADR-014; remediation suggestions are applied per ADR-042 if residual exceeds appetite.
131
+
132
+ ## Idempotency
133
+
134
+ The skill is idempotent by construction:
135
+
136
+ - Present files are skipped unless `--force`.
137
+ - Re-running the skill on a fully-scaffolded project produces no diff.
138
+ - The done marker prevents the host first-run prompt from re-firing.
139
+
140
+ ## Rule 6 audit (per ADR-032)
141
+
142
+ Every `AskUserQuestion` branch this skill uses must enumerate its AFK fail-safe per ADR-013 Rule 6. The audit table below documents each branch:
143
+
144
+ | AskUserQuestion branch | Resolution |
145
+ |---|---|
146
+ | Step 3: "Which files to scaffold?" (foreground invocation) | Foreground-synchronous — user is in-session; `AskUserQuestion` fires normally. |
147
+ | Step 4: "Overwrite outdated present file with updated template?" (with `--force`) | Foreground-synchronous; same as Step 3. |
148
+ | First-run prompt fired from `/wr-itil:manage-problem` or `/wr-itil:work-problems` (foreground invocation) | Foreground-synchronous per the hosting skill's pattern. |
149
+ | First-run prompt fired from an AFK orchestrator iteration | **Fail-safe (Rule 6)**: do NOT fire `AskUserQuestion` and do NOT auto-scaffold. Append a single one-line "pending intake scaffold" note to the orchestrator's iteration report; defer the prompt to the user's next interactive session. JTBD-006 forbids the agent from making this judgement call. |
150
+
151
+ ## Trigger surfaces (layered)
152
+
153
+ The skill is reachable via three trigger surfaces, layered so a soft prompt fires weeks before the hard publish stop. ADR-036 specifies the exact contract.
154
+
155
+ ### Trigger 1: First-run prompt from manage-problem / work-problems
156
+
157
+ When `/wr-itil:manage-problem` or `/wr-itil:work-problems` fires in a foreground session, the host skill's preamble checks:
158
+
159
+ - Is `.github/ISSUE_TEMPLATE/` missing OR are any of the four other intake files absent?
160
+ - Is `.claude/.intake-scaffold-declined` absent?
161
+ - Is `.claude/.intake-scaffold-done` absent?
162
+
163
+ When all three checks pass, the host emits a one-shot `AskUserQuestion` prompt with three options — **Scaffold now**, **Not now (ask again next session)**, **Decline (never prompt in this project)**. On "Decline", write `.claude/.intake-scaffold-declined` and never re-prompt unless the user deletes the marker.
164
+
165
+ **AFK fail-safe**: when the host is invoked from an AFK orchestrator, do NOT fire the prompt. Append a one-line "pending intake scaffold" note to the iteration report and continue. The user catches up on next interactive session.
166
+
167
+ ### Trigger 2: Pre-publish PreToolUse gate (hard stop)
168
+
169
+ `packages/itil/hooks/pre-publish-intake-gate.sh` matches `npm publish` and `gh pr merge ... changeset-release/*` and denies if any of the five intake files are missing AND the decline marker is absent AND `INTAKE_BYPASS` is not set.
170
+
171
+ **Override paths** (in priority order):
172
+ 1. `INTAKE_BYPASS=1 npm publish` — short-circuits the gate before existence check (consistent with `RISK_BYPASS` naming).
173
+ 2. `.claude/.intake-scaffold-declined` marker — explicit opt-out.
174
+ 3. Run `/wr-itil:scaffold-intake` to remove the cause.
175
+
176
+ ### Trigger 3: Optional CI check (deferred to v2)
177
+
178
+ `--ci` flag emits `.github/workflows/intake-check.yml` asserting the four files exist on every PR. Not shipped by default; layered ADD-on for adopters with GitHub Actions.
179
+
180
+ ## Idempotent re-runs and template drift
181
+
182
+ When this repo's intake files evolve (e.g., a future P066-style reform), downstream adopters' previously-scaffolded files stay frozen. Re-running the skill at any time picks up the diff: the present-and-outdated branch reports each file's diff and, with `--force`, performs diff-and-replace. Template drift policy (when this should fire automatically) is out of scope for v1 — see ADR-036 Reassessment Criteria.
183
+
184
+ ## Related
185
+
186
+ - [ADR-036](../../../../docs/decisions/036-scaffold-downstream-oss-intake.proposed.md) — design record (driver decision).
187
+ - [ADR-024](../../../../docs/decisions/024-cross-project-problem-reporting-contract.proposed.md) — sibling skill (`/wr-itil:report-upstream`); reciprocal pair.
188
+ - [ADR-032](../../../../docs/decisions/032-governance-skill-invocation-patterns.proposed.md) — foreground-synchronous pattern + Rule 6 audit requirement.
189
+ - [ADR-013](../../../../docs/decisions/013-structured-user-interaction-for-governance-decisions.proposed.md) — Rule 1 (interactive prompt) + Rule 6 (AFK fail-safe).
190
+ - [ADR-009](../../../../docs/decisions/009-gate-marker-lifecycle.proposed.md) — marker lifecycle (the `.intake-scaffold-{done,declined}` markers).
191
+ - [ADR-014](../../../../docs/decisions/014-governance-skills-commit-their-own-work.proposed.md) — commit discipline.
192
+ - P065 — driver ticket.
193
+ - P066 — parent (template seed source); the corrected problem-first intake shape this skill propagates.
194
+ - JTBD-301 — primary persona; downstream-project intake coverage.
195
+ - JTBD-101 — clear patterns for adopters extending the suite.
196
+ - JTBD-006 — AFK fail-safe constraint.
@@ -0,0 +1,31 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in {{project_name}}. This guide covers how to contribute code, decisions, and bug reports.
4
+
5
+ ## Before you start
6
+
7
+ - **Problems**: open an issue first using the **Report a problem** template under `.github/ISSUE_TEMPLATE/`. You do not need to pre-classify it as a bug or a feature -- this project practises ITIL problem management, so triage decides the category. Drive-by PRs without an issue are harder to merge because the problem framing is missing.
8
+ - **Security vulnerabilities**: do not open a public issue. See [SECURITY.md](SECURITY.md).
9
+ - **Usage questions**: use [Discussions]({{project_url}}/discussions), not issues.
10
+
11
+ ## Pull requests
12
+
13
+ 1. **Branch off the default branch**.
14
+ 2. **Make your change** with tests where applicable.
15
+ 3. **Open the PR** with a clear description of what is changing and why.
16
+
17
+ ### Commit style
18
+
19
+ - Follow Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, etc.).
20
+ - Reference problem tickets in the commit message when the work closes one.
21
+ - Sign-off is not required.
22
+
23
+ ## Code of conduct
24
+
25
+ Be kind. Engage in good faith. Critique ideas, not people. Maintainers reserve the right to lock or remove discussions, issues, or PRs that derail into personal attacks.
26
+
27
+ ## License
28
+
29
+ By contributing, you agree your contributions are licensed under {{project_name}}'s license. See the project root for the canonical LICENSE file.
30
+
31
+ Copyright (c) {{year}} the {{project_name}} contributors.
@@ -0,0 +1,39 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ **Do not file a public issue for security vulnerabilities.**
6
+
7
+ {{security_contact}} to disclose privately. Maintainers receive an email; the report stays private until a fix ships.
8
+
9
+ A useful report includes:
10
+
11
+ - The affected version of {{project_name}} (e.g. `{{project_name}}@x.y.z`).
12
+ - The runtime / platform you reproduced on.
13
+ - Reproduction steps that demonstrate the impact.
14
+ - Your assessment of impact (data loss, code execution, escalation, secret leak, etc.).
15
+ - Any suggested fix or mitigation.
16
+
17
+ ## What's in scope
18
+
19
+ Code shipped from this repository: {{project_url}}.
20
+
21
+ ## What's out of scope
22
+
23
+ - Vulnerabilities in third-party dependencies -- report to their maintainers.
24
+ - Issues that require an attacker to already have local code-execution on the user's machine.
25
+
26
+ ## Disclosure timeline
27
+
28
+ We aim to:
29
+
30
+ - **Acknowledge** the report within 7 calendar days.
31
+ - **Provide an initial assessment** (in scope, severity, planned fix) within 14 days.
32
+ - **Ship a fix** within 90 days for confirmed vulnerabilities, with most resolved sooner.
33
+ - **Coordinate disclosure** with the reporter so a public advisory and credit can be published when the fix lands.
34
+
35
+ If we cannot meet a 90-day timeline, we will say so in the advisory thread before the deadline.
36
+
37
+ ## Credit
38
+
39
+ Reporters who follow this private-disclosure path are credited in the published advisory unless they ask to remain anonymous.
@@ -0,0 +1,32 @@
1
+ # Getting Help
2
+
3
+ Where to go depends on what you need:
4
+
5
+ ## Usage questions, configuration help, pattern advice
6
+
7
+ [GitHub Discussions]({{project_url}}/discussions). Discussions stay searchable for the next person who hits the same question.
8
+
9
+ Before posting, search existing discussions and the project README.
10
+
11
+ ## Report a problem
12
+
13
+ [Open an issue]({{project_url}}/issues/new/choose) using the **Report a problem** template. You do not need to pre-classify it as a bug or feature -- this project practises ITIL problem management, and triage decides whether the root cause is a defect, a missing capability, a documentation gap, or something else. Describe what you observed and let triage decide the category.
14
+
15
+ Include:
16
+
17
+ - A description of what is happening and what you expected
18
+ - Observable symptoms (errors, command output, transcripts, screenshots -- redact secrets)
19
+ - Any workaround you tried, even if it did not help
20
+ - Affected component and version of {{project_name}}
21
+ - Runtime / platform and operating system
22
+ - Minimal reproduction steps or evidence
23
+
24
+ The template prompts for these. Issues without the template fields are slower to triage.
25
+
26
+ ## Security vulnerabilities
27
+
28
+ **Do not open a public issue.** {{security_contact}} for private disclosure. See [SECURITY.md](SECURITY.md) for the disclosure timeline and what's in scope.
29
+
30
+ ## Project documentation
31
+
32
+ Start with the README at the root of {{project_url}} for project-specific behaviour.
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Ask a question (GitHub Discussions)
4
+ url: {{project_url}}/discussions
5
+ about: For usage help, configuration questions, and pattern advice. The issue tracker is for problems -- triage decides whether the root cause is a defect, a missing capability, or something else.
6
+ - name: Report a security vulnerability
7
+ url: {{project_url}}/security/advisories/new
8
+ about: Use GitHub Security Advisories for private disclosure. Do not file a public issue. See SECURITY.md.
@@ -0,0 +1,87 @@
1
+ name: Report a problem
2
+ description: Report a problem with {{project_name}}. You do not need to pre-classify it as a bug or feature -- triage decides.
3
+ title: "[problem] "
4
+ labels: ["problem", "needs-triage"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for reporting a problem. This project practises ITIL problem management -- you describe what you observed, and triage decides whether the root cause is a defect, a missing capability, a documentation gap, or something else. You do not need to choose in advance.
10
+
11
+ For security vulnerabilities, **do not file here** -- use [GitHub Security Advisories]({{project_url}}/security/advisories/new) instead. For usage questions, use [Discussions]({{project_url}}/discussions).
12
+
13
+ - type: textarea
14
+ id: description
15
+ attributes:
16
+ label: Description
17
+ description: What is happening? What did you expect instead? Describe the observation, not the fix.
18
+ placeholder: |
19
+ e.g. "When I run the tool in a fresh project, it reports no items even though the source data has them. I expected it to list them."
20
+ validations:
21
+ required: true
22
+
23
+ - type: textarea
24
+ id: symptoms
25
+ attributes:
26
+ label: Symptoms
27
+ description: Observable behaviour -- error messages, command output, transcript snippets, screenshots. Redact secrets.
28
+ placeholder: |
29
+ - Command returns exit code 1
30
+ - Output contains "project root not found"
31
+ - ...
32
+ validations:
33
+ required: true
34
+
35
+ - type: textarea
36
+ id: workaround
37
+ attributes:
38
+ label: Workaround
39
+ description: Anything you tried that got you unstuck, even if temporary. "None found" is a valid answer.
40
+ placeholder: |
41
+ Retried after restarting -- same result. No workaround found.
42
+
43
+ - type: input
44
+ id: affected-component
45
+ attributes:
46
+ label: Affected component
47
+ description: Which part of {{project_name}} is involved? "multiple" or "unsure" is a valid answer -- triage will re-scope.
48
+ placeholder: "e.g. CLI / UI / specific module"
49
+ validations:
50
+ required: true
51
+
52
+ - type: input
53
+ id: frequency
54
+ attributes:
55
+ label: Frequency
56
+ description: How often does this happen? (every time, intermittently, once, ...)
57
+ placeholder: "e.g. every time I run the command"
58
+ validations:
59
+ required: true
60
+
61
+ - type: textarea
62
+ id: environment
63
+ attributes:
64
+ label: Environment
65
+ description: Version, runtime, and operating system.
66
+ placeholder: |
67
+ - {{project_name}} version: x.y.z
68
+ - Runtime: Node 20.x / Python 3.12 / ...
69
+ - OS: macOS 15.2
70
+ validations:
71
+ required: true
72
+
73
+ - type: textarea
74
+ id: evidence
75
+ attributes:
76
+ label: Evidence
77
+ description: Minimal reproduction steps, transcript links, screenshots, or related prior discussions. Feeds the investigation tasks triage will open.
78
+ placeholder: |
79
+ 1. Clone the repo
80
+ 2. Run the failing command
81
+ 3. Observe output
82
+
83
+ - type: textarea
84
+ id: additional
85
+ attributes:
86
+ label: Additional context
87
+ description: Anything else relevant -- related tickets, prior art, a hypothesis you already formed.
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # P065 / ADR-036 / ADR-037 Confirmation: scaffold-intake SKILL.md must
4
+ # encode the contract documented in ADR-036's Decision Outcome.
5
+ #
6
+ # Doc-lint structural test (Permitted Exception per ADR-005 — structural
7
+ # SKILL.md content checks, not behavioural). Mirrors the report-upstream
8
+ # contract bats pattern and the ADR-037 "Source review (at implementation
9
+ # time)" requirement that every shipped skill ships at least one
10
+ # `<skill>-contract.bats`.
11
+
12
+ setup() {
13
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
14
+ SKILL_MD="$REPO_ROOT/packages/itil/skills/scaffold-intake/SKILL.md"
15
+ TEMPLATE_DIR="$REPO_ROOT/packages/itil/skills/scaffold-intake/templates"
16
+ }
17
+
18
+ @test "scaffold-intake: SKILL.md exists" {
19
+ [ -f "$SKILL_MD" ]
20
+ }
21
+
22
+ @test "scaffold-intake: SKILL.md frontmatter declares the skill name" {
23
+ run grep -F 'name: wr-itil:scaffold-intake' "$SKILL_MD"
24
+ [ "$status" -eq 0 ]
25
+ }
26
+
27
+ @test "scaffold-intake: SKILL.md cross-references ADR-036 (driver decision)" {
28
+ run grep -F 'ADR-036' "$SKILL_MD"
29
+ [ "$status" -eq 0 ]
30
+ }
31
+
32
+ @test "scaffold-intake: SKILL.md cites ADR-032 foreground-synchronous pattern" {
33
+ run grep -F 'ADR-032' "$SKILL_MD"
34
+ [ "$status" -eq 0 ]
35
+ run grep -iE 'foreground.synchronous' "$SKILL_MD"
36
+ [ "$status" -eq 0 ]
37
+ }
38
+
39
+ @test "scaffold-intake: SKILL.md contains an explicit Rule 6 audit section (ADR-032 + ADR-013 Rule 6)" {
40
+ # ADR-032 line 191 requires every skill that uses AskUserQuestion to
41
+ # carry an enumerable Rule 6 audit. ADR-036 lines 92-97 ship the table
42
+ # shape.
43
+ run grep -iE 'rule 6 audit|rule-6 audit' "$SKILL_MD"
44
+ [ "$status" -eq 0 ]
45
+ }
46
+
47
+ @test "scaffold-intake: SKILL.md enumerates the three trigger surfaces (ADR-036)" {
48
+ # Trigger 1: first-run prompt from manage-problem / work-problems.
49
+ # Trigger 2: pre-publish PreToolUse gate (hard stop).
50
+ # Trigger 3: optional CI check (deferred / --ci flag).
51
+ run grep -iE 'trigger 1|first-run' "$SKILL_MD"
52
+ [ "$status" -eq 0 ]
53
+ run grep -iE 'trigger 2|pre-publish' "$SKILL_MD"
54
+ [ "$status" -eq 0 ]
55
+ run grep -iE 'trigger 3|ci check|--ci' "$SKILL_MD"
56
+ [ "$status" -eq 0 ]
57
+ }
58
+
59
+ @test "scaffold-intake: SKILL.md cites the INTAKE_BYPASS env override (ADR-036)" {
60
+ run grep -F 'INTAKE_BYPASS' "$SKILL_MD"
61
+ [ "$status" -eq 0 ]
62
+ }
63
+
64
+ @test "scaffold-intake: SKILL.md cites both marker files (ADR-036 + ADR-009)" {
65
+ run grep -F '.claude/.intake-scaffold-done' "$SKILL_MD"
66
+ [ "$status" -eq 0 ]
67
+ run grep -F '.claude/.intake-scaffold-declined' "$SKILL_MD"
68
+ [ "$status" -eq 0 ]
69
+ }
70
+
71
+ @test "scaffold-intake: SKILL.md documents idempotent + --force semantics (ADR-036)" {
72
+ run grep -iE 'idempotent' "$SKILL_MD"
73
+ [ "$status" -eq 0 ]
74
+ run grep -F -- '--force' "$SKILL_MD"
75
+ [ "$status" -eq 0 ]
76
+ run grep -F -- '--dry-run' "$SKILL_MD"
77
+ [ "$status" -eq 0 ]
78
+ }
79
+
80
+ @test "scaffold-intake: SKILL.md enumerates the five required intake files (ADR-036 Detection 5)" {
81
+ for path in 'config.yml' 'problem-report.yml' 'SECURITY.md' 'SUPPORT.md' 'CONTRIBUTING.md'; do
82
+ run grep -F "$path" "$SKILL_MD"
83
+ [ "$status" -eq 0 ] || { echo "missing reference: $path"; return 1; }
84
+ done
85
+ }
86
+
87
+ @test "scaffold-intake: SKILL.md documents AFK fail-safe (no auto-scaffold) per JTBD-006" {
88
+ # JTBD-006 + ADR-013 Rule 6: AFK orchestrator branch must NOT auto-write
89
+ # template files. The skill documents this as a Rule 6 fail-safe.
90
+ run grep -iE 'afk|rule 6' "$SKILL_MD"
91
+ [ "$status" -eq 0 ]
92
+ run grep -iE 'no auto.?scaffold|do not auto.?scaffold|silent note|pending.intake.scaffold' "$SKILL_MD"
93
+ [ "$status" -eq 0 ]
94
+ }
95
+
96
+ @test "scaffold-intake: SKILL.md cites ADR-014 commit discipline" {
97
+ run grep -F 'ADR-014' "$SKILL_MD"
98
+ [ "$status" -eq 0 ]
99
+ }
100
+
101
+ # --- Templates directory ---
102
+
103
+ @test "scaffold-intake: templates/ directory exists with five template seeds" {
104
+ [ -d "$TEMPLATE_DIR" ]
105
+ [ -f "$TEMPLATE_DIR/config.yml.tmpl" ]
106
+ [ -f "$TEMPLATE_DIR/problem-report.yml.tmpl" ]
107
+ [ -f "$TEMPLATE_DIR/SECURITY.md.tmpl" ]
108
+ [ -f "$TEMPLATE_DIR/SUPPORT.md.tmpl" ]
109
+ [ -f "$TEMPLATE_DIR/CONTRIBUTING.md.tmpl" ]
110
+ }
111
+
112
+ @test "scaffold-intake: problem-report.yml.tmpl is problem-first (P066 shape)" {
113
+ run grep -F 'title: "[problem] "' "$TEMPLATE_DIR/problem-report.yml.tmpl"
114
+ [ "$status" -eq 0 ]
115
+ run grep -F 'labels: ["problem", "needs-triage"]' "$TEMPLATE_DIR/problem-report.yml.tmpl"
116
+ [ "$status" -eq 0 ]
117
+ }
118
+
119
+ @test "scaffold-intake: templates use mustache-style substitution tokens (ADR-036)" {
120
+ # ADR-036 declares the token list. Each token must appear in at least
121
+ # one template file for the substitution surface to be wired.
122
+ for token in '{{project_name}}' '{{project_url}}' '{{security_contact}}'; do
123
+ run grep -rF "$token" "$TEMPLATE_DIR/"
124
+ [ "$status" -eq 0 ] || { echo "missing token: $token"; return 1; }
125
+ done
126
+ }
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # P065 / ADR-036 Confirmation behavioural-replay 1+2:
4
+ # Fixture-based behavioural test for scaffold-intake. Exercises the skill's
5
+ # core write-and-substitute contract against a mock empty downstream repo.
6
+ #
7
+ # This is the behavioural counterpart to scaffold-intake-contract.bats.
8
+ # It does NOT invoke the skill via Claude Code — it asserts the
9
+ # contract by replaying the skill's documented bash steps:
10
+ # 1. detect project name + url from package.json
11
+ # 2. enumerate missing intake files
12
+ # 3. write substituted templates to the correct paths
13
+ # 4. mark .claude/.intake-scaffold-done
14
+ # The bats reads templates/*.tmpl directly, applies the substitution rules
15
+ # defined in ADR-036, and asserts on the resulting files.
16
+ #
17
+ # Per feedback_behavioural_tests.md (P081): asserts observable file-system
18
+ # outcomes (files exist, content substituted, idempotent re-run no-ops),
19
+ # not source content.
20
+
21
+ setup() {
22
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
23
+ TEMPLATE_DIR="$REPO_ROOT/packages/itil/skills/scaffold-intake/templates"
24
+ ORIG_DIR="$PWD"
25
+ TEST_DIR=$(mktemp -d)
26
+ cd "$TEST_DIR"
27
+
28
+ # Seed a minimal package.json so the skill's detection step has inputs.
29
+ cat > package.json <<'JSON'
30
+ {
31
+ "name": "example-downstream",
32
+ "repository": {
33
+ "url": "https://github.com/example/downstream.git"
34
+ }
35
+ }
36
+ JSON
37
+ }
38
+
39
+ teardown() {
40
+ cd "$ORIG_DIR"
41
+ rm -rf "$TEST_DIR"
42
+ }
43
+
44
+ # Helper: replay the skill's substitute-and-write step for one template.
45
+ # This is the inline mustache-style substitution declared in ADR-036.
46
+ scaffold_one() {
47
+ local tmpl="$1"
48
+ local out="$2"
49
+ local project_name="example-downstream"
50
+ local project_url="https://github.com/example/downstream"
51
+ local security_contact="Use GitHub Security Advisories"
52
+ local plugin_list="@windyroad/itil"
53
+ local year="2026"
54
+ mkdir -p "$(dirname "$out")"
55
+ sed \
56
+ -e "s|{{project_name}}|$project_name|g" \
57
+ -e "s|{{project_url}}|$project_url|g" \
58
+ -e "s|{{security_contact}}|$security_contact|g" \
59
+ -e "s|{{plugin_list}}|$plugin_list|g" \
60
+ -e "s|{{year}}|$year|g" \
61
+ "$TEMPLATE_DIR/$tmpl" > "$out"
62
+ }
63
+
64
+ scaffold_all() {
65
+ scaffold_one "config.yml.tmpl" ".github/ISSUE_TEMPLATE/config.yml"
66
+ scaffold_one "problem-report.yml.tmpl" ".github/ISSUE_TEMPLATE/problem-report.yml"
67
+ scaffold_one "SECURITY.md.tmpl" "SECURITY.md"
68
+ scaffold_one "SUPPORT.md.tmpl" "SUPPORT.md"
69
+ scaffold_one "CONTRIBUTING.md.tmpl" "CONTRIBUTING.md"
70
+ mkdir -p .claude
71
+ : > .claude/.intake-scaffold-done
72
+ }
73
+
74
+ # --- Empty repo: scaffold writes all five files ---
75
+
76
+ @test "fixture: empty repo gains all five intake files" {
77
+ scaffold_all
78
+ [ -f .github/ISSUE_TEMPLATE/config.yml ]
79
+ [ -f .github/ISSUE_TEMPLATE/problem-report.yml ]
80
+ [ -f SECURITY.md ]
81
+ [ -f SUPPORT.md ]
82
+ [ -f CONTRIBUTING.md ]
83
+ }
84
+
85
+ @test "fixture: substitution tokens are resolved (no raw {{...}} left in scaffolded files)" {
86
+ scaffold_all
87
+ for f in \
88
+ .github/ISSUE_TEMPLATE/config.yml \
89
+ .github/ISSUE_TEMPLATE/problem-report.yml \
90
+ SECURITY.md \
91
+ SUPPORT.md \
92
+ CONTRIBUTING.md; do
93
+ run grep -F '{{' "$f"
94
+ [ "$status" -ne 0 ] || { echo "raw mustache token left in $f"; return 1; }
95
+ done
96
+ }
97
+
98
+ @test "fixture: project_name substitution propagated into scaffolded SECURITY.md or CONTRIBUTING.md" {
99
+ scaffold_all
100
+ # The project name should appear at least once in either SECURITY.md or
101
+ # CONTRIBUTING.md; the exact placement is template-dependent but
102
+ # ABSENCE from both files would indicate a substitution miss.
103
+ run bash -c "grep -F 'example-downstream' SECURITY.md SUPPORT.md CONTRIBUTING.md"
104
+ [ "$status" -eq 0 ]
105
+ }
106
+
107
+ @test "fixture: scaffolded problem-report.yml retains problem-first shape (P066)" {
108
+ scaffold_all
109
+ run grep -F 'title: "[problem] "' .github/ISSUE_TEMPLATE/problem-report.yml
110
+ [ "$status" -eq 0 ]
111
+ run grep -F 'labels: ["problem", "needs-triage"]' .github/ISSUE_TEMPLATE/problem-report.yml
112
+ [ "$status" -eq 0 ]
113
+ }
114
+
115
+ @test "fixture: done marker written after successful scaffold" {
116
+ scaffold_all
117
+ [ -f .claude/.intake-scaffold-done ]
118
+ }
119
+
120
+ # --- Idempotency: re-scaffolding produces no diff ---
121
+
122
+ @test "fixture: full re-application is idempotent (no diff)" {
123
+ scaffold_all
124
+ # Snapshot.
125
+ cp -R . "$TEST_DIR/.snapshot-1"
126
+ # Re-apply.
127
+ scaffold_all
128
+ # Diff against snapshot — exclude the snapshot dir itself + any tmp.
129
+ run diff -ru \
130
+ --exclude='.snapshot-1' \
131
+ --exclude='.git' \
132
+ "$TEST_DIR/.snapshot-1" "$TEST_DIR"
133
+ # diff exit 0 means no differences.
134
+ [ "$status" -eq 0 ]
135
+ }
136
+
137
+ # --- Partial repo: pre-existing CONTRIBUTING.md is preserved ---
138
+
139
+ @test "fixture: pre-existing CONTRIBUTING.md is preserved (idempotent skip)" {
140
+ echo "# Custom Contributing" > CONTRIBUTING.md
141
+ echo "Custom adopter content; must not be overwritten by scaffold." >> CONTRIBUTING.md
142
+ ORIGINAL=$(cat CONTRIBUTING.md)
143
+
144
+ # Scaffold the OTHER four files (skill skips the existing one).
145
+ scaffold_one "config.yml.tmpl" ".github/ISSUE_TEMPLATE/config.yml"
146
+ scaffold_one "problem-report.yml.tmpl" ".github/ISSUE_TEMPLATE/problem-report.yml"
147
+ scaffold_one "SECURITY.md.tmpl" "SECURITY.md"
148
+ scaffold_one "SUPPORT.md.tmpl" "SUPPORT.md"
149
+ # Deliberately do NOT call scaffold_one for CONTRIBUTING.md — that's
150
+ # the skill's idempotent skip behaviour for present files.
151
+
152
+ # Other files were created.
153
+ [ -f .github/ISSUE_TEMPLATE/config.yml ]
154
+ [ -f .github/ISSUE_TEMPLATE/problem-report.yml ]
155
+ [ -f SECURITY.md ]
156
+ [ -f SUPPORT.md ]
157
+
158
+ # CONTRIBUTING.md unchanged.
159
+ AFTER=$(cat CONTRIBUTING.md)
160
+ [ "$ORIGINAL" = "$AFTER" ]
161
+ }
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # P065 / ADR-036 / ADR-037 Confirmation: scaffold-intake's templates and
4
+ # SKILL.md must not leak absolute paths, host-specific identifiers, or
5
+ # secrets that would compromise downstream adopters who scaffold from
6
+ # them.
7
+ #
8
+ # Sentinel test: per ADR-037 "Source review", every skill that emits
9
+ # user-substituted content ships a `<skill>-secrets-absent.bats` to
10
+ # catch hardcoded environment leakage at PR time.
11
+
12
+ setup() {
13
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
14
+ TEMPLATE_DIR="$REPO_ROOT/packages/itil/skills/scaffold-intake/templates"
15
+ SKILL_MD="$REPO_ROOT/packages/itil/skills/scaffold-intake/SKILL.md"
16
+ }
17
+
18
+ # --- Templates: no absolute paths from the author's environment ---
19
+
20
+ @test "secrets-absent: templates contain no /Users/ or /home/ absolute paths" {
21
+ run grep -rE '/Users/[a-zA-Z0-9_-]+/' "$TEMPLATE_DIR/"
22
+ [ "$status" -ne 0 ]
23
+ run grep -rE '/home/[a-zA-Z0-9_-]+/' "$TEMPLATE_DIR/"
24
+ [ "$status" -ne 0 ]
25
+ }
26
+
27
+ @test "secrets-absent: templates contain no Windows-style absolute paths" {
28
+ run grep -rE '[A-Z]:\\Users\\' "$TEMPLATE_DIR/"
29
+ [ "$status" -ne 0 ]
30
+ }
31
+
32
+ # --- Templates: no obvious credential shapes ---
33
+
34
+ @test "secrets-absent: templates contain no credential-shaped tokens (AKIA, ghp_, sk_, github_pat_)" {
35
+ run grep -rE 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{80,}|sk_(live|test)_[A-Za-z0-9]{24,}' "$TEMPLATE_DIR/"
36
+ [ "$status" -ne 0 ]
37
+ }
38
+
39
+ @test "secrets-absent: templates contain no aws_access or aws_secret keys in plain text" {
40
+ run grep -rEi 'aws_access_key_id|aws_secret_access_key' "$TEMPLATE_DIR/"
41
+ [ "$status" -ne 0 ]
42
+ }
43
+
44
+ # --- Templates: name-of-this-repo must be substituted, not hardcoded ---
45
+
46
+ @test "secrets-absent: templates do not hardcode 'windyroad/agent-plugins' (must use {{project_url}}/{{project_name}} substitution)" {
47
+ # The author repo is windyroad/agent-plugins; downstream-scaffolded
48
+ # files must use the per-project substitution token, not the literal
49
+ # author repo. Otherwise downstream adopters get our SECURITY.md
50
+ # advisory URL pointing to OUR security advisories, which is wrong.
51
+ #
52
+ # Allow it ONLY in commentary lines that explain "templated from this
53
+ # repo's intake".
54
+ run grep -rl 'windyroad/agent-plugins' "$TEMPLATE_DIR/"
55
+ if [ "$status" -eq 0 ]; then
56
+ # Anything found must be inside a comment explicitly framing it as
57
+ # an example. Fail otherwise.
58
+ while read -r f; do
59
+ run grep -nE 'windyroad/agent-plugins' "$f"
60
+ while read -r line; do
61
+ line_no="${line%%:*}"
62
+ line_text="${line#*:}"
63
+ # Allow only if line begins with a comment marker (#) AND mentions example/seed.
64
+ if echo "$line_text" | grep -qE '^[[:space:]]*#' && echo "$line_text" | grep -qiE 'example|seed|template|reference'; then
65
+ continue
66
+ fi
67
+ echo "hardcoded windyroad/agent-plugins reference at $f:$line_no"
68
+ echo " $line_text"
69
+ return 1
70
+ done <<< "$output"
71
+ done < <(grep -rl 'windyroad/agent-plugins' "$TEMPLATE_DIR/")
72
+ fi
73
+ }
74
+
75
+ # --- SKILL.md: no transcript-leak shapes ---
76
+
77
+ @test "secrets-absent: SKILL.md contains no /Users/ or /home/ absolute paths" {
78
+ run grep -E '/Users/[a-zA-Z0-9_-]+/' "$SKILL_MD"
79
+ [ "$status" -ne 0 ]
80
+ run grep -E '/home/[a-zA-Z0-9_-]+/' "$SKILL_MD"
81
+ [ "$status" -ne 0 ]
82
+ }
83
+
84
+ @test "secrets-absent: SKILL.md contains no credential-shaped tokens" {
85
+ run grep -E 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{80,}' "$SKILL_MD"
86
+ [ "$status" -ne 0 ]
87
+ }
@@ -14,6 +14,24 @@ The user is AFK during this process, so every decision point that would normally
14
14
 
15
15
  Each iteration is one cycle of: scan backlog, pick highest-WSJF problem, work it, report result. The loop continues until a stop condition is met.
16
16
 
17
+ ## First-run intake-scaffold pointer (P065 / ADR-036)
18
+
19
+ This skill is one of the two host skills wired to surface the [`/wr-itil:scaffold-intake`](../scaffold-intake/SKILL.md) skill on first invocation in a project that has not yet adopted the OSS intake surface. The contract is documented in [ADR-036](../../../../docs/decisions/036-scaffold-downstream-oss-intake.proposed.md) (Scaffold downstream OSS intake — skill + layered triggers).
20
+
21
+ **Preamble check** (run once at session start, before Step 0 of the loop):
22
+
23
+ 1. Look for the four intake paths: `.github/ISSUE_TEMPLATE/config.yml`, `.github/ISSUE_TEMPLATE/problem-report.yml`, `SECURITY.md`, `SUPPORT.md`, `CONTRIBUTING.md`.
24
+ 2. Look for `.claude/.intake-scaffold-declined` (explicit decline marker — never re-prompt).
25
+ 3. Look for `.claude/.intake-scaffold-done` (done marker — already scaffolded).
26
+
27
+ If any intake file is missing AND both markers are absent: this skill is **always invoked from an AFK orchestrator context** (per the skill's allowed-tools and persona). The Rule 6 fail-safe applies unconditionally:
28
+
29
+ - Do **not** fire `AskUserQuestion`.
30
+ - Do **not** auto-scaffold.
31
+ - Append a one-line `"pending intake scaffold"` note to the iteration's `ITERATION_SUMMARY` notes field. The note is a per-iteration audit trail signal — accumulating one line per AFK iter is acceptable per ADR-036 § Bad consequences and JTBD-006 "audit trail — every action taken during AFK mode should be traceable".
32
+
33
+ The user reviews the pending note on their next interactive session and runs `/wr-itil:scaffold-intake` (or `/wr-itil:manage-problem` with the foreground prompt branch) at that point. JTBD-006 forbids the agent from making this judgement call autonomously.
34
+
17
35
  ### Step 0: Preflight (per ADR-019)
18
36
 
19
37
  Before opening the work loop, reconcile local state with origin so the orchestrator does not iterate against a stale backlog or create tickets with IDs that collide with parallel sessions (P040).
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # P065 / ADR-036 Confirmation line 199: work-problems SKILL.md must wire
4
+ # the first-run intake-scaffold pointer + AFK fail-safe + marker contract.
5
+ #
6
+ # Doc-lint structural test (Permitted Exception per ADR-005). Sister bats
7
+ # to manage-problem-first-run-intake-prompt.bats.
8
+
9
+ setup() {
10
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
11
+ SKILL_MD="$REPO_ROOT/packages/itil/skills/work-problems/SKILL.md"
12
+ }
13
+
14
+ @test "work-problems: SKILL.md cites ADR-036 (first-run-prompt contract)" {
15
+ run grep -F 'ADR-036' "$SKILL_MD"
16
+ [ "$status" -eq 0 ]
17
+ }
18
+
19
+ @test "work-problems: SKILL.md cross-references the scaffold-intake skill" {
20
+ run grep -F 'wr-itil:scaffold-intake' "$SKILL_MD"
21
+ [ "$status" -eq 0 ]
22
+ }
23
+
24
+ @test "work-problems: SKILL.md documents the AFK fail-safe (no auto-scaffold; iteration-report pending-note)" {
25
+ # AFK orchestrator branch must NOT auto-scaffold; instead the iteration
26
+ # appends a one-line "pending intake scaffold" note to its summary.
27
+ # The SKILL.md preamble pointer documents this so AFK consumers see
28
+ # the divergence at glance.
29
+ run grep -iE 'pending.intake.scaffold|pending intake scaffold|silent note' "$SKILL_MD"
30
+ [ "$status" -eq 0 ]
31
+ }
32
+
33
+ @test "work-problems: SKILL.md cites the decline marker path (ADR-009 + ADR-036)" {
34
+ run grep -F '.claude/.intake-scaffold-declined' "$SKILL_MD"
35
+ [ "$status" -eq 0 ]
36
+ }