Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
31 changes: 10 additions & 21 deletions .github/scripts/v4/publish-v4-channel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,20 @@ SHA="${4:?Need the commit sha to anchor the release.}"
METADATA_ZIP="${5:-}"

RAW_VERSION="${TEMPLATE_TAG#templates@}"
RAW_MINOR="$(echo "$RAW_VERSION" | cut -d. -f2)"

# Clean-version invariant. The v4 channel only ever holds clean versions that a
# `~major.minor` range can resolve, keyed on the minted version's odd/even minor:
# - ODD minor = prerelease line (6.11.x): published under a clean, date-stamped
# version (6.11.<date>) computed by computeV4PublishVersion and recorded as
# templates-config.json v4.localVersion by the preceding "sync v4 template
# config" step. We reuse that single source of truth here.
# - EVEN minor = stable line (6.10.x): already clean, published as-is.
# An EVEN-minor version that still carries a prerelease suffix means a preview
# lane was minted on a stable branch (the exact 6.10.3-beta.<date> misfire) —
# refuse it, since stripping the suffix would collide with the real stable 6.10.3.
case "$RAW_VERSION" in
*-*)
if [ $((RAW_MINOR % 2)) -eq 0 ]; then
echo "Refusing v4 channel publish: '$RAW_VERSION' is a prerelease-suffixed even-minor (stable) version — a preview lane minted on a stable branch." >&2
exit 1
fi
;;
esac

# Reuse the clean publish version the sync step already computed (single source
# of truth), falling back to the raw version for an even-minor stable release.
# `~major.minor` range can resolve. The clean publish version was computed by
# computeV4PublishVersion and recorded as templates-config.json v4.localVersion
# by the preceding "sync v4 template config" step; we reuse that single source
# of truth. A preview (-beta.<date> suffix) maps to the odd-minor line
# (6.11.<date>) — bumped from an even-minor stable base when needed, mirroring
# the VSIX vsc-version.sh mints; a stable version is already clean.
CONFIG_FILE="packages/fx-core/src/common/templates-config.json"
VERSION="$(node -p "(require('./$CONFIG_FILE').v4 || {}).localVersion || '$RAW_VERSION'")"
if [[ "$VERSION" == *-* || "$VERSION" == *+* ]]; then
echo "v4 publish version must be clean, got '$VERSION'." >&2
exit 1
fi

TAG="templates-v4@$VERSION"
NDJSON="$TMP/template-v4-tags.ndjson"
Expand Down
26 changes: 14 additions & 12 deletions .github/scripts/v4/sync-v4-template-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,22 @@ function computeBundled(goproduct) {
return !goproduct;
}

// Clean, suffix-free version published to the v4 channel: odd minor (prerelease)
// stamps the build date into the patch (6.11.<date>, read from the -beta.<date>
// preid); even minor (stable) uses major.minor.patch as-is.
// Clean, suffix-free version published to the v4 channel: mirrors the VSIX
// version vsc-version.sh mints. A preview (-beta.<date> suffix) targets the
// odd-minor line, date-stamped patch (6.11.<date>) — an even-minor stable base
// bumps to the next odd minor, like the VSIX; stable (no date) is as-is.
function computeV4PublishVersion(version) {
const parsed = semver.parse(version);
if (parsed === null) {
throw new Error(`Cannot compute v4 publish version: "${version}" is not valid SemVer.`);
}
if (parsed.minor % 2 === 1) {
const dateStamp = parsed.prerelease.find(
(segment) => typeof segment === "number" && segment >= 1000000000
);
if (dateStamp !== undefined) {
return `${parsed.major}.${parsed.minor}.${dateStamp}`;
}
const dateStamp =
parsed.prerelease[0] === "beta"
? parsed.prerelease.find((segment) => typeof segment === "number" && segment >= 1000000000)
: undefined;
if (dateStamp !== undefined) {
const minor = parsed.minor % 2 === 0 ? parsed.minor + 1 : parsed.minor;
return `${parsed.major}.${minor}.${dateStamp}`;
}
return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
}
Expand All @@ -79,10 +80,11 @@ function computeRange(version, previousRange) {
}

function computeV4TemplateConfig(input) {
const localVersion = computeV4PublishVersion(input.version);
return {
range: computeRange(input.version, input.previousRange),
range: computeRange(localVersion, input.previousRange),
bundled: computeBundled(input.goproduct),
localVersion: computeV4PublishVersion(input.version),
localVersion,
};
}

Expand Down
32 changes: 23 additions & 9 deletions .github/skills/vibe-coding/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ The non-negotiable gates per PR:

1. **Requirements first** — start from a GitHub Issue, ADO Work Item, chat request, or [`prd-ux-design`](../prd-ux-design/SKILL.md) handoff; confirm approved PRD + scenario artifacts exist before specs. If PRD or scenario work is needed, complete `prd-ux-design` first.
2. **Spec first after requirements are clear** — Operation Spec / Domain Spec / ADR / data-model entity in [`docs/03-specs/`](../../../docs/03-specs/README.md) written or located **before** any code is touched.
3. **Tests next** — every required test is derived 1:1 from the spec's `## Acceptance Criteria` table and includes the AC ID in its name (`it("AC-01: ...")`). Each AC row is tagged with its tier (L1 / L2 / L3); L2 CLI E2E and L3 VS Code UI tests are documented but not hard PR gates yet.
3. **Tests next** — every required test is derived 1:1 from the spec's `## Acceptance Criteria` table and includes the AC ID in its name (`it("AC-01: ...")`). Each AC row is tagged with its runtime tier (L1 / L2 / L3), purpose tier, gate, and harness; L2 CLI E2E and L3 VS Code UI tests are documented but not hard PR gates yet unless the touched behavior is the surface itself. For the purpose axis (operation-integration / scenario / compatibility / file-unit / surface), see [ADR-0013](../../../docs/02-architecture/adr/ADR-0013-test-tiering-and-coverage-gate.md) and the [test-tiers section](../../../docs/03-specs/README.md).
4. **Architectural and per-package rules followed** — implementation respects the ADRs in [`docs/02-architecture/`](../../../docs/02-architecture/README.md) and the matching files under [`.github/instructions/`](../../instructions/) for every path touched. Specifics (composition pattern, error model, input validation, registries, context propagation) live in those documents, not in this skill.
5. **New Template added to the template registry has a scaffold integration test**; CLI E2E coverage is documented as L2 but not a hard PR gate yet.
6. **Lint clean, format clean, 80% coverage gate green.**
5. **Changed user-visible workflows have scenario coverage** — a new template, lifecycle flow, migration path, CLI command behavior, VS Code command behavior, or other feature workflow has a Scenario Spec and a scenario-tier test for every required L1 AC row; L2/L3 surface coverage is documented and becomes a required gate when the changed behavior is the surface itself.
6. **Lint clean, format clean, coverage gate green.** "Coverage green" means every Acceptance-Criteria row has its owning AC-derived test (operation-integration, scenario, or compatibility), complex pure logic may add optional file-unit tests, and every thin adapter / glue / barrel is covered by one real integration test **or** carries a justified `/* istanbul ignore next -- <reason> */`. The numeric line floor is a backstop, not the definition of done — see [ADR-0013](../../../docs/02-architecture/adr/ADR-0013-test-tiering-and-coverage-gate.md) and the [test-tiers section](../../../docs/03-specs/README.md). Mock-heavy micro-units added solely to lift a file's line % are a review red flag.
7. **Conventional Commits** (`feat(<scope>):`, `fix(<scope>):` where `<scope>` is the package or domain touched, e.g. `feat(fx-core):`, `fix(cli):`).
8. **Downstream docs that describe shape** (template registry, driver catalogue, CLI surface) updated **in the same PR** when shape changes.

Expand Down Expand Up @@ -78,6 +78,7 @@ For any non-trivial change, locate or write the spec **after requirements are cl
| Change kind | Where the spec lives |
|-------------|---------------------|
| New / changed Operation, driver, or lifecycle stage | `docs/03-specs/operations/<domain>/<operation>.md` (see [`docs/03-specs/`](../../../docs/03-specs/README.md)) |
| New / changed user-visible workflow | `docs/03-specs/scenarios/<group>/<slug>.md` (feature workflow; scaffold template is one subtype) |
| New Domain or domain boundary change | `docs/03-specs/domains/<nn>-<domain>.md` (see [`docs/03-specs/`](../../../docs/03-specs/README.md)) |
| Architectural decision | New ADR under [`docs/02-architecture/`](../../../docs/02-architecture/README.md) (see that folder for the ADR convention) |
| New / changed data contract or entity | `docs/03-specs/data-model/entities/` (see [`docs/03-specs/`](../../../docs/03-specs/README.md)) |
Expand Down Expand Up @@ -136,12 +137,18 @@ it("AC-01: returns clientId and objectId on success", ...)
it("AC-03: returns UserError(AadAppNameTooLong) when name exceeds limit", ...)
```

**Each AC row in the spec carries a Tier**:
**Each AC row in the spec carries a Runtime, Purpose, Gate, and Harness**:

- **L1 — Engine** (unit + integration; per-PR, fast). Within L1, integration is weighted over unit for lifecycle code.
- **L2 — E2E** (real CLI against real M365 + Azure). **Documented but not a hard PR gate yet.**
- **L3 — UI** (VS Code wizard / command palette / webview flows). **Documented but not a hard PR gate yet.**

Purpose and harness decide what the test protects and how it runs. Use the
smallest harness that can falsify the AC: operation port, in-memory workflow,
temp dir, driver fake, CLI command harness, VS Code command harness, Playwright,
or compatibility diff harness. Compatibility AC rows are required whenever the
spec promises v3/v4, old/new, or migration parity.

The ADRs that formalize the tiering and the inverted lifecycle test pyramid live under [`docs/02-architecture/`](../../../docs/02-architecture/README.md) as they land.

Map from what you'll implement to what test it needs:
Expand All @@ -150,9 +157,11 @@ Map from what you'll implement to what test it needs:
|---|---|---|
| Pure function (no I/O) | Unit test (`tests/unit/<area>/`) | L1 (unit) |
| Engine action with side effects (operation, driver, lifecycle stage) | Integration test exercising full pipeline (`tests/integration/`) — mock only outermost HTTP | L1 (integration) |
| New Template added to the template registry | Scaffold integration test; document L2 E2E scenario if applicable | L1 (integration); L2 documented |
| New CLI action | CLI integration test; document L2 E2E scenario if it writes resources | L1 (integration); L2 documented |
| New VS Code command / wizard | Handler unit test; document L3 UI scenario if user-visible | L1 (unit); L3 documented |
| New or changed feature workflow | Scenario-tier integration test asserting observable workflow outcomes | L1 (scenario) |
| New Template added to the template registry | Scenario-tier scaffold test; document L2 CLI matrix case if applicable | L1 (scenario); L2 documented |
| Compatibility or migration promise | Normalized old/new or v3/v4 diff test | L1/L2 (compatibility) |
| New CLI action | CLI integration test; document L2 E2E scenario if it writes resources | L1/L2, depending on boundary |
| New VS Code command / wizard | Handler or command-level test; document L3 UI scenario if user-visible | L1 command; L3 documented |

A unit test that only re-mocks what an integration test already covers is a delete signal.

Expand Down Expand Up @@ -207,6 +216,7 @@ Mechanical gates do not catch quality. Before claiming done, audit the diff agai
1. **ADRs in [`docs/02-architecture/`](../../../docs/02-architecture/README.md)** — every new file/function must respect them. Common slips show up in file size, in module-scoped runtime state, and in error-handling shape.
2. **Matching `.github/instructions/*`** — for each touched file path, the instruction file whose `applyTo` matches should be re-checked. Common slips: copyright header missing, `as` cast snuck in, raw user-facing string instead of `getLocalizedString`, secret not masked.
3. **The Anti-patterns list at the bottom of this skill** — flag any match and fix before PR.
4. **Single source of truth / no transient-doc rot** — if the diff added or touched a doc: (a) it must not restate a fact that already has an owner elsewhere — link to the owner instead; (b) any time-bound page it added (a `*.current-state.md`, an ADR-decomposition proposal) must carry an `Expires-when:` header naming the ADRs whose all-`Accepted` state retires it. See the conventions in [`docs/02-architecture/`](../../../docs/02-architecture/README.md).

Output of this phase is either "all checks pass" or a list of concrete fixes → loop back to Phase 3.

Expand All @@ -233,7 +243,9 @@ CODEOWNERS will auto-assign reviewers. Reviewers will check:
- ✅ Spec exists in `docs/03-specs/` and is referenced in the PR.
- ✅ Tests carry AC IDs and trace 1:1 to AC rows.
- ✅ Integration test exists for any new engine action with side effects (operation, driver, lifecycle stage).
- ✅ Scaffold integration test for any new template registered in the template registry; L2 E2E scenario documented when applicable.
- ✅ Scenario-tier test exists for every changed user-visible workflow AC; scaffold templates are covered as workflow scenarios, not a special exception.
- ✅ Compatibility diff test exists for any migration, v3/v4, or old/new parity promise.
- ✅ Surface smoke is documented for changed CLI / VS Code behavior and required when the surface behavior itself changed.
- ✅ ADRs in [`docs/02-architecture/`](../../../docs/02-architecture/README.md) and matching `.github/instructions/*` followed for every path touched.
- ✅ Lint / format / coverage gates green.
- ✅ Downstream docs updated where the drift checklist required.
Expand All @@ -251,7 +263,9 @@ CODEOWNERS will auto-assign reviewers. Reviewers will check:
## Anti-patterns to flag in self-review

- A new engine action with side effects but no integration test — required.
- A new template in the template registry without a scaffold integration test.
- A changed user-visible workflow without a Scenario Spec and scenario-tier test.
- A new template in the template registry without a scenario-tier scaffold test.
- A migration promise without a normalized compatibility diff test.
- A test name without an AC ID prefix — required if the test maps to a spec.
- A "TODO: add tests later" comment — not allowed; tests are written in Phase 3.
- A "TODO: add docs later" comment — not allowed; this is the doc-PR.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ re-points.
the existing L1/L2/L3 *runtime* tags, and AC-derived integration coverage, not
per-file line execution, is the unit-of-measure the gate exists to defend.

Three purpose tiers:
Three primary purpose tiers, plus one migration-specific subtype:

- **operation-integration** (AC-derived) — the primary protected tier. One test
per acceptance-criterion row, run through the operation's injected port with
Expand All @@ -112,10 +112,15 @@ Three purpose tiers:
never to chase a line number.
- **scenario / CLI-E2E / UI** (L2/L3) — protect cross-component and surface
behavior; documented now, progressively gated later.
- **compatibility** — an AC-derived subtype used when a spec promises migration,
v3/v4, or old/new parity. It protects the promise with a normalized diff so
intentional differences are explicit and accidental drift is visible.

Decision rule for a contributor facing an uncovered line:

- Uncovered **behavior** → add its operation-integration test (AC row).
- Uncovered **migration or parity promise** → add its compatibility diff test
(AC row).
- Uncovered **complex pure logic** → optionally add a file-unit test.
- Uncovered **thin adapter / glue / barrel** → cover it with *one* real
integration test across the real boundary (temp dir; stub only at the network
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,12 @@ and v3 and v4 coexist behind it.
**`dispatch` keys off `templateId` only** — it never reads `descriptor.languages` and never branches on
language (the v3/v4 registry choice is a function of `templateId` alone).

`language` is a **separate BuildTarget axis**, not part of route resolution.
It is resolved by the stage that already holds the chosen template's
`descriptor` (descriptor-bound, not dispatcher-bound), anywhere inside the
window **[templateId/descriptor resolved] .. [before `content/{language}/`
render]**: its legal values are bounded by `descriptor.languages`, it is
auto-skipped when that lists a single language (both MCP scenarios:
`"languages": ["common"]`), and the exact prompt position inside the window
(immediately after routing, or deferred past Q2) is a surface/UX choice, not
an engine contract. The resulting `BuildTarget = { templateId, language? }`
feeds the rest of the flow (Q2 → pipeline / v3 generator), which is identical
regardless of source. This keeps `resolveBuildTarget` a pure route
resolver — a caller-supplied `language` (Source B/C) rides along untouched;
only the interactive surface prompts for it, and only after a descriptor is
in hand. **(Amended 2026-06-15 — Amendment 2: `language` leaves `BuildTarget`
entirely and is resolved as the Q0 `language` question (ADR-0016 decision 5)
in the collect-inputs walk; `resolveBuildTarget` no longer reads
`descriptor.languages` and binds no language axis.)**
`language` is **not** part of route resolution or `BuildTarget`. It is
resolved after the template is chosen, as the Q0 `language` question
(ADR-0016 decision 5) in the collect-inputs walk; `resolveBuildTarget` no
longer reads `descriptor.languages` and binds no language axis. The resulting
`BuildTarget = { templateId, engine, answers }` feeds the rest of the flow
(Q2 → pipeline / v3 generator), which is identical regardless of source.

2. **Each `selector.json` route declares its `engine`** — the closed set is
`{ v4, v3, v3-core-method, surface-action }` (invariant 12,
Expand Down
2 changes: 1 addition & 1 deletion docs/02-architecture/scaffolding.backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
ADR.** A modify template is the same four-file package (`descriptor` /
`questions` / `pipeline` / optional `content`) under `templates/v4/modify/<id>/`,
resolved by the same dispatcher into the same
`BuildTarget = { templateId, language? }` ([ADR-0014](adr/ADR-0014-dispatcher-buildtarget-resolution.md)),
`BuildTarget = { templateId, engine, answers }` ([ADR-0014](adr/ADR-0014-dispatcher-buildtarget-resolution.md)),
run by the same two-phase executor (`render new files → post-render steps`,
[ADR-0017](adr/ADR-0017-named-pipeline-step-whitelist.md)). `kind` is only a
routing / telemetry label: it selects which per-kind `selector.json` runs (Q1)
Expand Down
Loading
Loading