From 7b65b1815ee4269cfd82495fd1e00606aebe5a7b Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Fri, 14 Aug 2026 21:28:49 +1200 Subject: [PATCH 01/14] Pin the merge method per direction, and show rebase is safe here MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squash into develop, rebase into main. GitHub cannot enforce a method per branch, so this is written down rather than configured; both methods stay enabled because both are correct somewhere. Also records why rebasing between two long-lived branches is safe, since it is a fair thing to worry about: rebase-merge rewrites commits, so main never becomes an ancestor of develop, and after a hotfix has been ported back the merge base falls behind both. The next release therefore *looks* like it would replay commits already on main. It doesn't — git rebase drops already-applied commits by patch-id. Verified rather than assumed: after a release, a hotfix on main and a cherry-pick back to develop, rebasing develop onto main listed three candidate commits and replayed exactly one. The edge case is documented too: a port-back that was conflict-resolved differently no longer matches by patch, so rebase will retry it. That shows up as a conflict at release time, which is visible and fixable rather than silent. Co-Authored-By: Claude Opus 5 (1M context) --- .github/PULL_REQUEST_TEMPLATE.md | 5 +++++ docs/branching-and-release.md | 26 ++++++++++++++++++++++++-- docs/releasing.md | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d43304e..0fc9b4a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,3 +8,8 @@ + + + + + diff --git a/docs/branching-and-release.md b/docs/branching-and-release.md index c95f1c0..8623255 100644 --- a/docs/branching-and-release.md +++ b/docs/branching-and-release.md @@ -120,9 +120,31 @@ Admins are deliberately exempt (`enforce_admins: false`), which keeps an escape ## Merging -Merge commits are disabled. **Squash or rebase only**, and linear history is enforced on all protected branches. +**Merge commits are disabled** and linear history is enforced on every protected branch. Which of the two remaining methods to use depends on the direction: -One consequence specific to GitFlow: a `release/*` or `hotfix/*` branch has to merge into **two** branches. Squashing into `main` and then squashing the same work into `develop` produces two unrelated commits with the same content, which is fine here — we don't rely on `git branch --contains` for anything except [tag validation](ci.md#publishyml), which checks reachability from `main` and `support/*` only. +| Merging | Method | Why | +|---|---|---| +| `feature/*` → `develop` | **Squash** | Working branches accumulate WIP. One commit per landed change keeps the trunk readable. | +| `develop` → `main` | **Rebase** | Squashing would collapse an entire release into a single commit on the production branch, losing the per-change history. | +| `release/*` → `main` | **Rebase** | Same, and the individual stabilisation commits are what you cherry-pick back to `develop`. | +| `hotfix/*` → `main` | **Rebase** | Same — you need a real commit to port back. | +| anything → `develop` (port-back) | **Squash** | It's a working branch like any other. | + +GitHub can't enforce a method per branch, so this is discipline rather than configuration. Both methods stay enabled because both are correct somewhere. + +### Why rebase across two long-lived branches is safe here + +Rebase-merge rewrites commits, so `main` never becomes an ancestor of `develop` — and once a hotfix has landed on `main` and been ported back, the merge base falls behind both. The obvious worry is that the *next* release would try to replay commits already present on `main`. + +It doesn't. `git rebase` detects already-applied commits by patch-id and drops them, so a second release replays only the genuinely new work. Verified rather than assumed: after a release, a hotfix on `main`, and a cherry-pick back to `develop`, a rebase of `develop` onto `main` listed three candidate commits and replayed exactly one. + +The edge case to know: if a port-back was **conflict-resolved differently** from the original, its patch no longer matches and rebase will try to apply it again. That surfaces as a conflict at release time — visible and fixable, not silent. + +### The double merge-back + +A `release/*` or `hotfix/*` branch has to reach **two** branches. Because merge commits are disabled, the second one is a cherry-pick or a fresh PR rather than a literal merge — see [releasing.md](releasing.md#hotfix). The effect is what matters: a fix that never reaches `develop` ships once and then disappears on the next release. + +We don't rely on `git branch --contains` anywhere except [tag validation](ci.md#publishyml), which checks reachability from `main` and `support/*` only — so the SHA divergence between the two branches costs us nothing. ## See also diff --git a/docs/releasing.md b/docs/releasing.md index 9b7415c..60e2571 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -112,7 +112,7 @@ gh release create v10.4.30-rc.1 --target main --prerelease --generate-notes The tag must match the version `nbgv` computes for that commit — the workflow packages from the checked-out tag, not from the tag name, so a mismatch ships a `.vsix` whose version disagrees with its release. -> **Merge with rebase, not squash, for `develop` → `main`.** Squashing collapses a whole release into one commit, which loses the per-change history on the production branch and makes later comparisons between the two branches useless. Merge commits are disabled repo-wide and linear history is enforced, so rebase is the only option that keeps commits intact. +> **Merge with rebase, not squash, into `main`.** Squashing collapses a whole release into one commit, losing the per-change history on the production branch. Merge commits are disabled and linear history is enforced, so rebase is the option that keeps commits intact. Repeated rebase-merges stay clean across releases — `git rebase` drops already-applied commits by patch-id. Full reasoning and the one edge case in [branching-and-release.md](branching-and-release.md#merging). > **"Merge back to develop" is a cherry-pick or a second PR here**, not a literal merge. GitFlow assumes merge commits; this repo enforces linear history. The effect is the same — the fix must reach `develop`, or it ships to users and then disappears on the next release — but the mechanism is a PR carrying the same changes. From aa2dedb645a0d12fec81e4b57df2f4771e58dc9c Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 16 Aug 2026 12:57:57 +1200 Subject: [PATCH 02/14] Adopt AGENTS.md as the canonical brief for AI coding tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One canonical AGENTS.md, read natively by GitHub Copilot, with CLAUDE.md as a pointer that imports it — matching the framework repo so contributors and tooling meet one convention across both. Adds CONTRIBUTING.md, a shared issue-and-PR style contract, issue templates, and the repo's Claude Code config. Adapted rather than copied: the merge model differs here, so the commit-restructuring skill now reads the PR's base ref and stops on develop-bound PRs, where squash makes commit curation pointless. Ignores *.local.* so machine-local state can't be committed. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/agents/story-writer.md | 42 +++++++ .claude/commands/new-issue.md | 40 +++++++ .../skills/restructure-pr-commits/SKILL.md | 112 ++++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 111 +++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 14 +++ .github/ISSUE_TEMPLATE/feature_idea.yml | 53 +++++++++ .github/ISSUE_TEMPLATE/user_story.yml | 61 ++++++++++ .gitignore | 16 +++ .vscodeignore | 4 + AGENTS.md | 94 +++++++++++++++ CLAUDE.md | 5 + CONTRIBUTING.md | 80 +++++++++++++ README.md | 2 + docs/issue-and-pr-style.md | 54 +++++++++ 14 files changed, 688 insertions(+) create mode 100644 .claude/agents/story-writer.md create mode 100644 .claude/commands/new-issue.md create mode 100644 .claude/skills/restructure-pr-commits/SKILL.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_idea.yml create mode 100644 .github/ISSUE_TEMPLATE/user_story.yml create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 CONTRIBUTING.md create mode 100644 docs/issue-and-pr-style.md diff --git a/.claude/agents/story-writer.md b/.claude/agents/story-writer.md new file mode 100644 index 0000000..0641140 --- /dev/null +++ b/.claude/agents/story-writer.md @@ -0,0 +1,42 @@ +--- +name: story-writer +description: Drafts GitHub issues and user stories for this repo — terse, outcome-focused, and to the canonical shape. Use when asked to create, write, or file an issue/story. +tools: Read, Grep, Glob, Bash +model: sonnet +--- + +You write GitHub issues and user stories for the Fallout VS Code extension repo. +Your output is the issue body itself, not a conversation about it. + +**Before writing**, read `docs/issue-and-pr-style.md` — it is the binding style +contract. Follow it exactly. + +Defaults: + +- Use the **Problem → Outcome → Acceptance criteria** shape. Drop any section + that doesn't apply rather than padding it. +- Be terse. Lead with the point. No preamble, no restating the title, no + hedging, no marketing tone, no emoji headers. Match length to substance. +- Prefer linking (`#123`, `src/model.ts:64`) over pasting. +- Outcomes describe observable behaviour, not implementation. + +Scope check before you write: this repo is the **extension**, not the framework. +Anything about target execution, the build engine, or how `build-graph.json` is +*produced* belongs in [Fallout-build/Fallout](https://github.com/Fallout-build/Fallout). +This repo owns how that graph is *displayed and driven* from VS Code. Say so +rather than filing it in the wrong place. + +For bugs, the report is not actionable without three versions — VS Code, +extension, and Fallout. The extension needs **Fallout 10.4.0+**; below that no +`build-graph.json` is emitted at all, which accounts for most "empty view" +reports. Rule that out before writing it up as an extension bug. + +When the request is underspecified, ask at most 1–2 sharp questions, then write. +Do not invent acceptance criteria the user didn't imply — leave a `- [ ]` stub +if unknown. + +If asked to file it, run `gh issue create` with `--title` and a `--body` that +matches the shape. Apply exactly one category label — `enhancement` for stories +unless told otherwise; the taxonomy is in `.github/release.yml`. This repo has +no `target/YYYY` labels; that is a framework-repo convention. Report the created +issue URL and nothing else. diff --git a/.claude/commands/new-issue.md b/.claude/commands/new-issue.md new file mode 100644 index 0000000..3d95db6 --- /dev/null +++ b/.claude/commands/new-issue.md @@ -0,0 +1,40 @@ +--- +description: Draft (and optionally file) a terse, outcome-focused GitHub issue to the repo's canonical shape. +argument-hint: +allowed-tools: Read, Bash(gh issue create:*), Bash(gh label list:*) +--- + +Read `docs/issue-and-pr-style.md` and follow it as the binding style contract. +Then draft a GitHub issue for: **$ARGUMENTS** + +Assemble the body to the canonical shape: + +```markdown +### Problem +<1–2 sentences> + +### Outcome + + +### Acceptance criteria +- [ ] +- [ ] +``` + +Rules: + +- Terse. Lead with the point. No preamble, no restating the title, no filler. +- Drop `Acceptance criteria` (and add a short `### Notes`) only if it doesn't + fit the ask. Don't invent criteria — leave `- [ ]` stubs if unknown. +- Prefer links (`#123`, `src/model.ts:64`) over pasted blocks. +- For a bug, include the three versions that make a report actionable here: + VS Code, extension, and Fallout. Most "the view is empty" reports are a + pre-10.4.0 framework emitting no `build-graph.json` at all — rule that out + before writing it up as an extension bug. + +Show me the drafted title and body first. **Do not file it until I confirm.** +On confirmation, run `gh issue create --title "…" --body "…"` with exactly one +category label (`enhancement`, `bug`, `breaking-change`, `security`, +`documentation`, `dependencies`) — the taxonomy in `.github/release.yml`. This +repo has no `target/YYYY` labels; that is a framework-repo convention. Report +only the resulting issue URL. diff --git a/.claude/skills/restructure-pr-commits/SKILL.md b/.claude/skills/restructure-pr-commits/SKILL.md new file mode 100644 index 0000000..ce942f9 --- /dev/null +++ b/.claude/skills/restructure-pr-commits/SKILL.md @@ -0,0 +1,112 @@ +--- +name: restructure-pr-commits +description: "Restructure the commits on an existing PR into focused, reviewable commits" +--- + +Restructure the commits on PR `` into focused, reviewable commits. +Follow these steps in order exactly. + +## 0 — Check this is worth doing at all + +``` +gh pr view --json baseRefName,title,url +``` + +**The base branch decides whether commit curation matters.** This repo enforces +linear history with merge commits disabled, and the merge method differs by +direction (`docs/branching-and-release.md#merging`): + +- **Base `main`** — merged with **rebase**. Every commit lands on the production + branch verbatim and becomes a permanent `git bisect` target. Curation matters; + proceed. +- **Base `develop`** — merged with **squash**. The whole PR collapses to one + commit no matter how you arrange it. Restructuring is wasted effort — the + thing that actually needs care is the **PR title**, since that is what lands + on the trunk and what the release notes quote. Say so and stop, unless the + user explicitly wants the branch history tidied anyway. + +Use the real base ref from that command as `` below — do not assume. + +## 1 — Find what the PR actually changes + +``` +git diff origin/ HEAD --stat +``` + +This is the authoritative list of files the PR modifies. `git log` is misleading +on branches kept up to date via merge: merge commits drag base-branch history +into the log as if it were authored on this branch — it isn't. + +## 2 — Find the branch's own commits + +``` +git log --first-parent --no-merges origin/..HEAD --oneline +``` + +This shows only commits made directly on this branch. + +## 3 — Create a backup branch + +``` +git branch backup-pr- HEAD +``` + +Do this before changing anything. Never skip this step. + +## 4 — Build the restructured history + +Check out a fresh branch from the base: +``` +git checkout origin/ -b restructured-pr- +``` + +Then restore the changed files from the backup: +``` +git checkout backup-pr- -- +``` + +Handle renames explicitly: `git rm ` then `git checkout backup-pr- -- `. + +Commit in logical groups — one concern per commit. Commit message rules (from +`docs/issue-and-pr-style.md`): +- Functional imperative phrases; no conventional-commit prefixes (`fix:`, `feat:`, etc.) +- Subject completes "This commit will…" without saying so +- Body (when needed) explains *why*, not *what*; no file/class/type names + +Do not restructure across the generated/hand-written CI boundary: a change to +`build/Build.CI.GitHubActions.cs` and the regenerated `.github/workflows/build.yml` +belong in the **same** commit. Splitting them leaves a commit where the attribute +and the workflow disagree, which is exactly the state the generated-file rule +exists to prevent. + +## 5 — Verify file content is unchanged + +Run both and report the output: +``` +git diff HEAD backup-pr- +git diff HEAD origin/ --stat +``` + +The first command must produce **no output**; if it does, stop and investigate before continuing. +The second must list **exactly the same files** as step 1. + +## 6 — Stop and wait for explicit confirmation + +Show the new `git log --oneline` and ask the user to confirm the history looks +correct. Do not proceed until the user explicitly says yes. + +## 7 — Push with --force-with-lease only after confirmation + +``` +git push origin HEAD: --force-with-lease +``` + +Never use `--force` alone. + +## 8 — Update the PR title and description after confirmation + +Rewrite the PR title and description to match the restructured commits. +Structure: Summary → one Changes section per commit → optional Combined effect. +Each Changes section maps 1-to-1 to a commit. Keep the one category label +correct — it is the changelog (`.github/release.yml`), and the title is the +release-note line. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ba28bd0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,111 @@ +name: 🐞 Bug Report +description: "Report something that doesn't look alright." +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + _Hi there :wave: and thanks for taking the time to report a bug!_ + + _Before you continue, please check the [contribution guidelines](https://github.com/Fallout-build/Fallout.Extensions.VSCode/blob/develop/CONTRIBUTING.md)._ + + - type: markdown + attributes: + value: | + **Empty Targets view?** Check two things first — they account for most reports: + 1. Your workspace builds with **Fallout 10.4.0 or later**. Earlier versions emit no build graph at all. + 2. `.fallout/temp/build-graph.json` exists. Run `./build.ps1 --plan` once to generate it. + + - type: input + id: usage-information + attributes: + label: Usage Information + description: Extension version / Fallout version / VS Code version / Operating System + placeholder: 10.4.30 / Fallout 10.4.0 / VS Code 1.96.2 / macOS 15.2 + validations: + required: true + + - type: textarea + id: description + attributes: + label: Description + description: Please share a clear and concise description of the problem. + placeholder: Description + validations: + required: true + + - type: textarea + id: reproduction-steps + attributes: + label: Reproduction Steps + description: | + Minimal steps to reproduce. Include logs and exceptions as text rather than screenshots — except for genuinely visual problems (graph layout, tree icons, theme contrast), where a screenshot is the clearest evidence. + placeholder: Minimal Reproduction + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + placeholder: Expected Behavior + validations: + required: true + + - type: textarea + id: actual-behavior + attributes: + label: Actual Behavior + description: If applicable, include error messages or stack traces. + placeholder: Actual Behavior + validations: + required: true + + - type: textarea + id: build-graph + attributes: + label: Build graph + description: | + For anything about which targets appear, how they relate, or how the graph renders: paste the relevant slice of `.fallout/temp/build-graph.json`. Trim it to the targets involved — the whole file is rarely needed. + render: json + validations: + required: false + + - type: textarea + id: extension-log + attributes: + label: Extension log + description: | + Optional. **Output** panel → select **Fallout** or **Log (Extension Host)** from the dropdown. Errors thrown during activation land there. + render: shell + validations: + required: false + + - type: textarea + id: regression + attributes: + label: Regression? + description: | + Did this work in a previous version of the extension? If you can try an earlier one, that helps narrow it down. If you don't know, that's OK. + placeholder: Regression? + validations: + required: false + + - type: textarea + id: known-workarounds + attributes: + label: Known Workarounds + placeholder: Known Workarounds + validations: + required: false + + - type: dropdown + id: pull-request + attributes: + label: Could you help with a pull-request? + description: See the [contribution guidelines](https://github.com/Fallout-build/Fallout.Extensions.VSCode/blob/develop/CONTRIBUTING.md). + options: + - "No" + - "Yes" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..47b2d9f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: 🔒 Security vulnerability + url: https://github.com/Fallout-build/.github/blob/main/SECURITY.md + about: Don't file these as issues. Report privately via this repo's Security tab. + - name: 🧩 Fallout framework issues + url: https://github.com/Fallout-build/Fallout/issues + about: Build engine, targets, or how build-graph.json is produced — that's the framework, not this extension. + - name: 📚 Contributing & release docs + url: https://github.com/Fallout-build/Fallout.Extensions.VSCode/blob/develop/CONTRIBUTING.md + about: Branching, PR labels, and how this extension is built and released. + - name: 🧪 Preview build + url: https://github.com/Fallout-build/Fallout.Extensions.VSCode/releases/tag/preview + about: Every push to develop publishes a rolling pre-release .vsix. Worth checking your issue isn't already fixed there. diff --git a/.github/ISSUE_TEMPLATE/feature_idea.yml b/.github/ISSUE_TEMPLATE/feature_idea.yml new file mode 100644 index 0000000..6f7d713 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_idea.yml @@ -0,0 +1,53 @@ +name: 💡 Feature Idea +description: "Suggest new features for the extension." +labels: ["enhancement", "triage"] +body: + - type: markdown + attributes: + value: | + _Hi there :wave: and thanks for taking the time to send a feature idea!_ + + _Before you continue, please check the [contribution guidelines](https://github.com/Fallout-build/Fallout.Extensions.VSCode/blob/develop/CONTRIBUTING.md)._ + + _This repo is the **VS Code extension**. Ideas about targets, the build engine, or how the build graph is produced belong in the [framework repo](https://github.com/Fallout-build/Fallout/issues)._ + + - type: textarea + attributes: + label: Description + description: What should the new feature do? + validations: + required: true + + - type: textarea + attributes: + label: Usage Example + description: How would you use it — which view, command, or gesture? + + - type: textarea + attributes: + label: Alternative + description: What do you do today instead? + + - type: dropdown + id: needs-framework-change + attributes: + label: Does this need new data in build-graph.json? + description: | + If the extension would need information Fallout doesn't currently emit, this needs a framework change first and will be routed there. Pick "Not sure" if you don't know — that's fine. + options: + - "Not sure" + - "No — it works with what's already in the graph" + - "Yes — Fallout would need to emit something new" + validations: + required: true + + - type: dropdown + id: pull-request + attributes: + label: Could you help with a pull-request? + description: See the [contribution guidelines](https://github.com/Fallout-build/Fallout.Extensions.VSCode/blob/develop/CONTRIBUTING.md). + options: + - "No" + - "Yes" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/user_story.yml b/.github/ISSUE_TEMPLATE/user_story.yml new file mode 100644 index 0000000..6f7b0dd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/user_story.yml @@ -0,0 +1,61 @@ +name: 📝 User Story +description: "Describe a desired capability as a small, outcome-focused story." +labels: ["enhancement", "triage"] +body: + - type: markdown + attributes: + value: | + _Keep it terse and scannable — Problem, Outcome, and how we'll know it's done. See [issue & PR style](https://github.com/Fallout-build/Fallout.Extensions.VSCode/blob/develop/docs/issue-and-pr-style.md)._ + + _Before you continue, please check the [contribution guidelines](https://github.com/Fallout-build/Fallout.Extensions.VSCode/blob/develop/CONTRIBUTING.md)._ + + - type: textarea + id: problem + attributes: + label: Problem + description: What's wrong or missing, and for whom? 1–2 sentences. + placeholder: Finding which target a failing build actually ran means reading the terminal scrollback — the Targets view doesn't show run state. + validations: + required: true + + - type: textarea + id: outcome + attributes: + label: Outcome + description: What does "done" look like? Describe observable behaviour, not the implementation. + placeholder: A target that has run shows its last result in the tree, and clicking it reveals that run's output. + validations: + required: true + + - type: textarea + id: acceptance-criteria + attributes: + label: Acceptance criteria + description: Testable checkboxes. Drop this section if the Outcome is self-evident. + placeholder: | + - [ ] A succeeded target is visually distinct from one that has never run + - [ ] State clears when the graph is regenerated + value: | + - [ ] + - [ ] + validations: + required: false + + - type: textarea + id: notes + attributes: + label: Notes + description: Optional. Links, constraints, related issues (≤3 lines). Leave blank if none. + validations: + required: false + + - type: dropdown + id: pull-request + attributes: + label: Could you help with a pull-request? + description: See the [contribution guidelines](https://github.com/Fallout-build/Fallout.Extensions.VSCode/blob/develop/CONTRIBUTING.md). + options: + - "No" + - "Yes" + validations: + required: true diff --git a/.gitignore b/.gitignore index a0343ba..2ba810b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,19 @@ out/ .fallout/temp/ bin/ obj/ + +# Local-only, by convention: anything named *.local.* is this-machine state and never +# committed. Covers CLAUDE.local.md (personal agent directives), settings.local.json, +# and any *.local.json/.xml a tool drops beside its shared counterpart. The shared +# sibling — settings.json, AGENTS.md, CLAUDE.md — IS committed. +*.local.* + +# Claude Code — local state the pattern above doesn't catch. Credentials especially: +# these must never reach a public repo. +.claude/.credentials* +.claude/scheduled_tasks.lock +.claude/worktrees/ +memory/ + +# macOS +.DS_Store diff --git a/.vscodeignore b/.vscodeignore index 9fe2cfb..2076b1e 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,6 +1,7 @@ .vscode/** .vscode-test/** .github/** +.claude/** src/** out/**/*.map **/*.ts @@ -20,6 +21,9 @@ build.ps1 build.sh # Maintainer-facing; the marketplace page renders README.md only. docs/** +AGENTS.md +CLAUDE.md +CONTRIBUTING.md # Ship only Mermaid's self-contained UMD bundle (loaded by the graph webview via a # classic + +`; + } +} + +function getNonce(): string { + let text = ''; + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return text; +} From ae4aaebd8cc08fa6fc2e4b19362392ba078a8ab2 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Mon, 17 Aug 2026 00:12:53 +1200 Subject: [PATCH 10/14] Add secrets to the Run Configuration, via SecretStorage (#19) Co-authored-by: Claude Opus 5 --- src/runConfig.ts | 78 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/src/runConfig.ts b/src/runConfig.ts index eb2f7e6..df1a27c 100644 --- a/src/runConfig.ts +++ b/src/runConfig.ts @@ -7,11 +7,15 @@ export interface Parameter { } const PARAMS_KEY = 'fallout.parameters'; +const SECRET_NAMES_KEY = 'fallout.secretNames'; +const SECRET_PREFIX = 'fallout.secret.'; /** - * Persists the local run configuration. Parameters live in workspace state — they are - * per-workspace by nature (a configuration for *this* build), and plain enough to sit - * in plugin storage. They are rendered as CLI args (`--name value`) for a local run. + * Persists the local run configuration: parameters live in workspace state (plain, + * per-workspace); secret *values* live in VS Code's SecretStorage (OS keychain-backed) + * and are never rendered into the webview — only their names are. Parameters become + * CLI args (`--name value`); secrets become environment variables so they don't leak + * into shell history or the process list. */ export class RunConfigStore { private readonly onDidChangeEmitter = new vscode.EventEmitter(); @@ -36,19 +40,49 @@ export class RunConfigStore { this.onDidChangeEmitter.fire(); } + getSecretNames(): string[] { + return this.context.workspaceState.get(SECRET_NAMES_KEY, []); + } + + async setSecret(name: string, value: string): Promise { + await this.context.secrets.store(SECRET_PREFIX + name, value); + if (!this.getSecretNames().includes(name)) { + await this.context.workspaceState.update(SECRET_NAMES_KEY, [...this.getSecretNames(), name].sort()); + } + this.onDidChangeEmitter.fire(); + } + + async removeSecret(name: string): Promise { + await this.context.secrets.delete(SECRET_PREFIX + name); + await this.context.workspaceState.update(SECRET_NAMES_KEY, this.getSecretNames().filter(n => n !== name)); + this.onDidChangeEmitter.fire(); + } + /** Parameters rendered as a CLI argument string, e.g. `--configuration Release`. */ buildArgs(): string { return this.getParameters() .map(p => `--${p.name} ${quoteArg(p.value)}`) .join(' '); } + + /** Resolves all stored secrets into an environment map for a local run. */ + async buildEnv(): Promise> { + const env: Record = {}; + for (const name of this.getSecretNames()) { + const value = await this.context.secrets.get(SECRET_PREFIX + name); + if (value !== undefined) { + env[name] = value; + } + } + return env; + } } function quoteArg(value: string): string { return /\s/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value; } -/** The "Run Configuration" webview view — a form for build parameters. */ +/** The "Run Configuration" webview view — a form for parameters and secret names. */ export class RunConfigViewProvider implements vscode.WebviewViewProvider { private view: vscode.WebviewView | undefined; @@ -75,6 +109,12 @@ export class RunConfigViewProvider implements vscode.WebviewViewProvider { case 'removeParameter': await this.store.removeParameter(String(message.name)); break; + case 'setSecret': + if (message.name && message.value) { await this.store.setSecret(String(message.name), String(message.value)); } + break; + case 'removeSecret': + await this.store.removeSecret(String(message.name)); + break; } }); } @@ -83,6 +123,7 @@ export class RunConfigViewProvider implements vscode.WebviewViewProvider { void this.view?.webview.postMessage({ type: 'state', parameters: this.store.getParameters(), + secretNames: this.store.getSecretNames(), }); } @@ -106,6 +147,7 @@ export class RunConfigViewProvider implements vscode.WebviewViewProvider { button.icon { background: transparent; color: var(--vscode-foreground); padding: 3px 6px; } .name { flex: 0 0 40%; font-family: var(--vscode-editor-font-family); } .hint { opacity: 0.65; font-size: 11px; margin: 2px 0 8px; } + .secret-val { font-family: var(--vscode-editor-font-family); opacity: 0.6; } @@ -118,6 +160,15 @@ export class RunConfigViewProvider implements vscode.WebviewViewProvider { +

Secrets

+
Stored in the OS keychain (SecretStorage); passed as environment variables. Values are never shown.
+
+
+ + + +
+