@gordon.gan/specflow 1.1.1 → 1.2.0-beta.1

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.
Files changed (72) hide show
  1. package/README.md +34 -8
  2. package/dist/cli/commands/change-archive.js +4 -4
  3. package/dist/cli/commands/change-new.js +5 -5
  4. package/dist/cli/commands/change-phase.js +4 -4
  5. package/dist/cli/commands/change-status.js +4 -4
  6. package/dist/cli/commands/context.d.ts +18 -0
  7. package/dist/cli/commands/context.js +125 -0
  8. package/dist/cli/commands/init.d.ts +1 -0
  9. package/dist/cli/commands/init.js +20 -6
  10. package/dist/cli/commands/instructions.d.ts +4 -1
  11. package/dist/cli/commands/instructions.js +62 -9
  12. package/dist/cli/commands/show.d.ts +22 -0
  13. package/dist/cli/commands/show.js +92 -0
  14. package/dist/cli/commands/store.d.ts +16 -0
  15. package/dist/cli/commands/store.js +221 -0
  16. package/dist/cli/commands/validate.d.ts +16 -0
  17. package/dist/cli/commands/validate.js +34 -4
  18. package/dist/cli/commands/workset.d.ts +12 -0
  19. package/dist/cli/commands/workset.js +235 -0
  20. package/dist/cli/index.js +8 -0
  21. package/dist/cli/shared/store-option.d.ts +11 -0
  22. package/dist/cli/shared/store-option.js +40 -0
  23. package/dist/core/artifact-graph/instruction-loader.d.ts +23 -0
  24. package/dist/core/artifact-graph/instruction-loader.js +6 -0
  25. package/dist/core/artifact-graph/types.d.ts +2 -2
  26. package/dist/core/artifact-language.d.ts +7 -0
  27. package/dist/core/artifact-language.js +33 -0
  28. package/dist/core/context-assembly.d.ts +9 -0
  29. package/dist/core/context-assembly.js +68 -0
  30. package/dist/core/diagnostics.d.ts +11 -0
  31. package/dist/core/diagnostics.js +18 -0
  32. package/dist/core/file-state.d.ts +23 -0
  33. package/dist/core/file-state.js +101 -0
  34. package/dist/core/global-config.d.ts +26 -0
  35. package/dist/core/global-config.js +77 -0
  36. package/dist/core/opener-launch.d.ts +3 -0
  37. package/dist/core/opener-launch.js +20 -0
  38. package/dist/core/openers.d.ts +23 -0
  39. package/dist/core/openers.js +20 -0
  40. package/dist/core/project-config.d.ts +16 -0
  41. package/dist/core/project-config.js +104 -0
  42. package/dist/core/reference-index.d.ts +8 -0
  43. package/dist/core/reference-index.js +80 -0
  44. package/dist/core/references.d.ts +17 -0
  45. package/dist/core/references.js +51 -0
  46. package/dist/core/relationship-health.d.ts +22 -0
  47. package/dist/core/relationship-health.js +68 -0
  48. package/dist/core/root-selection.d.ts +26 -0
  49. package/dist/core/root-selection.js +197 -0
  50. package/dist/core/store/errors.d.ts +2 -0
  51. package/dist/core/store/errors.js +1 -0
  52. package/dist/core/store/foundation.d.ts +141 -0
  53. package/dist/core/store/foundation.js +79 -0
  54. package/dist/core/store/health.d.ts +13 -0
  55. package/dist/core/store/health.js +117 -0
  56. package/dist/core/store/operations.d.ts +56 -0
  57. package/dist/core/store/operations.js +268 -0
  58. package/dist/core/store/registry.d.ts +15 -0
  59. package/dist/core/store/registry.js +128 -0
  60. package/dist/core/working-set.d.ts +30 -0
  61. package/dist/core/working-set.js +26 -0
  62. package/dist/core/worksets.d.ts +71 -0
  63. package/dist/core/worksets.js +134 -0
  64. package/dist/integrations/shared/skill-renderer.d.ts +6 -0
  65. package/dist/integrations/shared/skill-renderer.js +22 -0
  66. package/package.json +1 -1
  67. package/prompts/shared/artifact-language.md +30 -0
  68. package/skills/specflow-apply/SKILL.md +12 -27
  69. package/skills/specflow-explore/SKILL.md +6 -0
  70. package/skills/specflow-propose/SKILL.md +6 -0
  71. package/skills/specflow-refine/SKILL.md +6 -0
  72. package/skills/specflow-snap/SKILL.md +6 -0
@@ -0,0 +1,128 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import yaml from 'js-yaml';
4
+ import { acquireFileLock, releaseFileLock, writeFileAtomically, makeLockErrorFactory, } from '../file-state.js';
5
+ import { StoreRegistrySchema, emptyRegistry, pathsReferToSameCheckout, StoreError, } from './foundation.js';
6
+ function lockPathFor(registryPath) {
7
+ return `${registryPath}.lock`;
8
+ }
9
+ function makeRegistryLockError() {
10
+ return makeLockErrorFactory({
11
+ createSubject: 'the registry lock file',
12
+ busyMessage: 'Store registry is busy.',
13
+ code: 'registry_busy',
14
+ target: 'stores.registry',
15
+ });
16
+ }
17
+ export async function readRegistry(registryPath) {
18
+ try {
19
+ const content = await fs.readFile(registryPath, 'utf-8');
20
+ const parsed = yaml.load(content);
21
+ const result = StoreRegistrySchema.safeParse(parsed);
22
+ if (!result.success) {
23
+ throw new StoreError('Store registry is corrupt.', {
24
+ severity: 'error',
25
+ code: 'registry_corrupt',
26
+ message: 'Store registry failed schema validation.',
27
+ target: 'stores.registry',
28
+ fix: 'Repair or delete the registry file and re-register stores.',
29
+ });
30
+ }
31
+ return result.data;
32
+ }
33
+ catch (error) {
34
+ if (error instanceof StoreError) {
35
+ throw error;
36
+ }
37
+ if (error.code === 'ENOENT') {
38
+ return emptyRegistry();
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+ async function writeRegistry(registryPath, state) {
44
+ const content = yaml.dump(state, { lineWidth: -1, noRefs: true });
45
+ await writeFileAtomically(registryPath, content);
46
+ }
47
+ export async function updateRegistry(registryPath, updater) {
48
+ const lock = await acquireFileLock({
49
+ lockPath: lockPathFor(registryPath),
50
+ errorFor: makeRegistryLockError(),
51
+ });
52
+ try {
53
+ const current = await readRegistry(registryPath);
54
+ const next = updater(current);
55
+ const validated = StoreRegistrySchema.parse(next);
56
+ await writeRegistry(registryPath, validated);
57
+ return validated;
58
+ }
59
+ finally {
60
+ await releaseFileLock(lock, lockPathFor(registryPath));
61
+ }
62
+ }
63
+ export function findPathConflict(registry, targetPath, storeId, platform = process.platform) {
64
+ for (const [existingId, entry] of Object.entries(registry.stores)) {
65
+ if (existingId === storeId) {
66
+ continue;
67
+ }
68
+ if (pathsReferToSameCheckout(entry.backend.local_path, targetPath, platform)) {
69
+ return existingId;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ export async function registerStoreEntry(registryPath, input) {
75
+ const canonicalPath = path.resolve(input.localPath);
76
+ let alreadyRegistered = false;
77
+ const registry = await updateRegistry(registryPath, (state) => {
78
+ const existing = state.stores[input.id];
79
+ if (existing) {
80
+ if (pathsReferToSameCheckout(existing.backend.local_path, canonicalPath)) {
81
+ alreadyRegistered = true;
82
+ return state;
83
+ }
84
+ throw new StoreError(`Store '${input.id}' is already registered at another path.`, {
85
+ severity: 'error',
86
+ code: 'store_id_conflict',
87
+ message: `Store '${input.id}' is already registered elsewhere.`,
88
+ target: `stores.${input.id}`,
89
+ fix: 'Unregister the existing checkout or choose another store ID.',
90
+ });
91
+ }
92
+ const aliasConflict = findPathConflict(state, canonicalPath, input.id);
93
+ if (aliasConflict) {
94
+ throw new StoreError(`Path already registered as '${aliasConflict}'.`, {
95
+ severity: 'error',
96
+ code: 'store_path_conflict',
97
+ message: `The checkout path is already registered under '${aliasConflict}'.`,
98
+ target: 'stores.registry',
99
+ fix: 'Use the existing registration or unregister the conflicting store first.',
100
+ });
101
+ }
102
+ return {
103
+ version: 1,
104
+ stores: {
105
+ ...state.stores,
106
+ [input.id]: {
107
+ provenance: input.provenance,
108
+ backend: {
109
+ type: 'git',
110
+ local_path: canonicalPath,
111
+ ...(input.remote ? { remote: input.remote } : {}),
112
+ ...(input.branch ? { branch: input.branch } : {}),
113
+ },
114
+ },
115
+ },
116
+ };
117
+ });
118
+ return { registry, alreadyRegistered };
119
+ }
120
+ export async function unregisterStoreEntry(registryPath, storeId) {
121
+ return updateRegistry(registryPath, (state) => {
122
+ if (!state.stores[storeId]) {
123
+ return state;
124
+ }
125
+ const { [storeId]: _removed, ...rest } = state.stores;
126
+ return { version: 1, stores: rest };
127
+ });
128
+ }
@@ -0,0 +1,30 @@
1
+ import type { Diagnostic } from './diagnostics.js';
2
+ export interface WorkingSetMember {
3
+ readonly storeId: string;
4
+ readonly path?: string;
5
+ readonly healthy: boolean;
6
+ readonly diagnostics: readonly Diagnostic[];
7
+ readonly fetchRecipe?: string;
8
+ }
9
+ export interface WorkingSet {
10
+ readonly members: readonly WorkingSetMember[];
11
+ }
12
+ export declare function assembleWorkingSet(input: {
13
+ root: {
14
+ storeId: string;
15
+ path: string;
16
+ healthy: boolean;
17
+ };
18
+ references: Array<{
19
+ storeId: string;
20
+ healthy: boolean;
21
+ path?: string;
22
+ diagnostics: Diagnostic[];
23
+ }>;
24
+ }): WorkingSet;
25
+ export declare function buildWorkingSetCodeWorkspaceJson(workingSet: WorkingSet): {
26
+ folders: Array<{
27
+ name: string;
28
+ path: string;
29
+ }>;
30
+ };
@@ -0,0 +1,26 @@
1
+ export function assembleWorkingSet(input) {
2
+ const members = [
3
+ {
4
+ storeId: input.root.storeId,
5
+ path: input.root.path,
6
+ healthy: input.root.healthy,
7
+ diagnostics: [],
8
+ fetchRecipe: `specflow context --store ${input.root.storeId}`,
9
+ },
10
+ ...input.references.map((ref) => ({
11
+ storeId: ref.storeId,
12
+ path: ref.healthy ? ref.path : undefined,
13
+ healthy: ref.healthy,
14
+ diagnostics: ref.diagnostics,
15
+ fetchRecipe: ref.healthy ? `specflow show <spec-id> --type spec --store ${ref.storeId}` : undefined,
16
+ })),
17
+ ];
18
+ return { members };
19
+ }
20
+ export function buildWorkingSetCodeWorkspaceJson(workingSet) {
21
+ return {
22
+ folders: workingSet.members
23
+ .filter((member) => member.healthy && member.path)
24
+ .map((member) => ({ name: member.storeId, path: member.path })),
25
+ };
26
+ }
@@ -0,0 +1,71 @@
1
+ import { z } from 'zod';
2
+ export interface WorksetMember {
3
+ readonly name: string;
4
+ readonly path: string;
5
+ }
6
+ export interface Workset {
7
+ readonly name: string;
8
+ readonly members: readonly WorksetMember[];
9
+ readonly tool?: string;
10
+ }
11
+ declare const WorksetsStateSchema: z.ZodObject<{
12
+ version: z.ZodLiteral<1>;
13
+ worksets: z.ZodArray<z.ZodObject<{
14
+ name: z.ZodString;
15
+ members: z.ZodArray<z.ZodObject<{
16
+ name: z.ZodString;
17
+ path: z.ZodString;
18
+ }, "strict", z.ZodTypeAny, {
19
+ path: string;
20
+ name: string;
21
+ }, {
22
+ path: string;
23
+ name: string;
24
+ }>, "many">;
25
+ tool: z.ZodOptional<z.ZodString>;
26
+ }, "strict", z.ZodTypeAny, {
27
+ name: string;
28
+ members: {
29
+ path: string;
30
+ name: string;
31
+ }[];
32
+ tool?: string | undefined;
33
+ }, {
34
+ name: string;
35
+ members: {
36
+ path: string;
37
+ name: string;
38
+ }[];
39
+ tool?: string | undefined;
40
+ }>, "many">;
41
+ }, "strip", z.ZodTypeAny, {
42
+ worksets: {
43
+ name: string;
44
+ members: {
45
+ path: string;
46
+ name: string;
47
+ }[];
48
+ tool?: string | undefined;
49
+ }[];
50
+ version: 1;
51
+ }, {
52
+ worksets: {
53
+ name: string;
54
+ members: {
55
+ path: string;
56
+ name: string;
57
+ }[];
58
+ tool?: string | undefined;
59
+ }[];
60
+ version: 1;
61
+ }>;
62
+ export type WorksetsState = z.infer<typeof WorksetsStateSchema>;
63
+ export declare function validateWorksetName(name: string): void;
64
+ export declare function validateWorksetMembers(members: readonly WorksetMember[]): void;
65
+ export declare function readWorksetsState(statePath: string): Promise<WorksetsState>;
66
+ export declare function updateWorksetsState(statePath: string, updater: (state: WorksetsState) => WorksetsState): Promise<WorksetsState>;
67
+ export declare function withWorkset(state: WorksetsState, workset: Workset): WorksetsState;
68
+ export declare function getWorksetCodeWorkspacePath(dataDir: string, name: string): string;
69
+ export declare function defaultWorksetsStatePath(dataDir?: string): string;
70
+ export declare function removeWorksetByName(statePath: string, name: string, dataDir: string): Promise<boolean>;
71
+ export {};
@@ -0,0 +1,134 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import yaml from 'js-yaml';
4
+ import { z } from 'zod';
5
+ import { acquireFileLock, releaseFileLock, writeFileAtomically, makeLockErrorFactory } from './file-state.js';
6
+ import { getWorksetsStatePath } from './global-config.js';
7
+ import { StoreError } from './store/foundation.js';
8
+ const KEBAB_CASE_NAME = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
9
+ const WorksetMemberSchema = z.object({ name: z.string(), path: z.string() }).strict();
10
+ const WorksetSchema = z
11
+ .object({
12
+ name: z.string(),
13
+ members: z.array(WorksetMemberSchema).min(1),
14
+ tool: z.string().optional(),
15
+ })
16
+ .strict();
17
+ const WorksetsStateSchema = z.object({
18
+ version: z.literal(1),
19
+ worksets: z.array(WorksetSchema),
20
+ });
21
+ function lockPathFor(statePath) {
22
+ return `${statePath}.lock`;
23
+ }
24
+ function makeWorksetLockError() {
25
+ return makeLockErrorFactory({
26
+ createSubject: 'the workset lock file',
27
+ busyMessage: 'Workset state is busy.',
28
+ code: 'workset_busy',
29
+ target: 'worksets.state',
30
+ });
31
+ }
32
+ export function validateWorksetName(name) {
33
+ if (!KEBAB_CASE_NAME.test(name)) {
34
+ throw new StoreError(`Invalid workset name '${name}'.`, {
35
+ severity: 'error',
36
+ code: 'invalid_workset_name',
37
+ message: 'Workset names must be kebab-case.',
38
+ target: 'workset.name',
39
+ });
40
+ }
41
+ }
42
+ export function validateWorksetMembers(members) {
43
+ if (members.length === 0) {
44
+ throw new StoreError('Workset must contain at least one member.', {
45
+ severity: 'error',
46
+ code: 'invalid_workset_members',
47
+ message: 'Worksets require at least one member.',
48
+ target: 'workset.members',
49
+ });
50
+ }
51
+ const labels = new Set();
52
+ for (const member of members) {
53
+ if (!path.isAbsolute(member.path)) {
54
+ throw new StoreError(`Member path must be absolute: ${member.path}`, {
55
+ severity: 'error',
56
+ code: 'invalid_workset_path',
57
+ message: 'Workset member paths must be absolute.',
58
+ target: `workset.members.${member.name}`,
59
+ });
60
+ }
61
+ if (labels.has(member.name)) {
62
+ throw new StoreError(`Duplicate workset member label '${member.name}'.`, {
63
+ severity: 'error',
64
+ code: 'duplicate_workset_member',
65
+ message: 'Workset member labels must be unique.',
66
+ target: `workset.members.${member.name}`,
67
+ });
68
+ }
69
+ labels.add(member.name);
70
+ }
71
+ }
72
+ export async function readWorksetsState(statePath) {
73
+ try {
74
+ const content = await fs.readFile(statePath, 'utf-8');
75
+ const parsed = yaml.load(content);
76
+ return WorksetsStateSchema.parse(parsed);
77
+ }
78
+ catch (error) {
79
+ if (error.code === 'ENOENT') {
80
+ return { version: 1, worksets: [] };
81
+ }
82
+ throw error;
83
+ }
84
+ }
85
+ async function writeWorksetsState(statePath, state) {
86
+ const content = yaml.dump(state, { lineWidth: -1, noRefs: true });
87
+ await writeFileAtomically(statePath, content);
88
+ }
89
+ export async function updateWorksetsState(statePath, updater) {
90
+ const lock = await acquireFileLock({
91
+ lockPath: lockPathFor(statePath),
92
+ errorFor: makeWorksetLockError(),
93
+ });
94
+ try {
95
+ const current = await readWorksetsState(statePath);
96
+ const next = WorksetsStateSchema.parse(updater(current));
97
+ await writeWorksetsState(statePath, next);
98
+ return next;
99
+ }
100
+ finally {
101
+ await releaseFileLock(lock, lockPathFor(statePath));
102
+ }
103
+ }
104
+ export function withWorkset(state, workset) {
105
+ validateWorksetName(workset.name);
106
+ validateWorksetMembers(workset.members);
107
+ const without = state.worksets.filter((entry) => entry.name !== workset.name);
108
+ return { version: 1, worksets: [...without, workset].sort((a, b) => a.name.localeCompare(b.name)) };
109
+ }
110
+ export function getWorksetCodeWorkspacePath(dataDir, name) {
111
+ return path.join(dataDir, 'worksets', `${name}.code-workspace`);
112
+ }
113
+ export function defaultWorksetsStatePath(dataDir) {
114
+ return getWorksetsStatePath(dataDir);
115
+ }
116
+ export async function removeWorksetByName(statePath, name, dataDir) {
117
+ let removed = false;
118
+ await updateWorksetsState(statePath, (state) => {
119
+ const exists = state.worksets.some((entry) => entry.name === name);
120
+ if (!exists) {
121
+ return state;
122
+ }
123
+ removed = true;
124
+ return {
125
+ version: 1,
126
+ worksets: state.worksets.filter((entry) => entry.name !== name),
127
+ };
128
+ });
129
+ if (removed) {
130
+ const workspacePath = getWorksetCodeWorkspacePath(dataDir, name);
131
+ await fs.rm(workspacePath, { force: true }).catch(() => undefined);
132
+ }
133
+ return removed;
134
+ }
@@ -1,4 +1,10 @@
1
1
  import type { IdeTarget } from './types.js';
2
+ import type { ReferencedStoresSection } from '../../core/artifact-graph/instruction-loader.js';
3
+ export interface ReferenceGuidanceInput {
4
+ readonly referencedStores?: ReferencedStoresSection;
5
+ }
6
+ /** Renders identical reference guidance for every IDE target. */
7
+ export declare function collectReferenceGuidance(input: ReferenceGuidanceInput): Record<IdeTarget, string>;
2
8
  export declare function renderIdeContent(content: string, ide: IdeTarget): string;
3
9
  /** @deprecated Use renderIdeContent */
4
10
  export declare const renderSkillContent: typeof renderIdeContent;
@@ -1,4 +1,26 @@
1
1
  import { COMMAND_CATALOG } from './command-catalog.js';
2
+ function formatReferenceBlock(section) {
3
+ if (!section || section.rendered.trim().length === 0) {
4
+ return '';
5
+ }
6
+ const lines = [
7
+ '## Referenced store index (read-only)',
8
+ section.rendered,
9
+ ];
10
+ if (section.truncated) {
11
+ lines.push('Index truncated. Use `specflow show <spec-id> --type spec --store <store-id>` to fetch full specs.');
12
+ }
13
+ return lines.join('\n');
14
+ }
15
+ /** Renders identical reference guidance for every IDE target. */
16
+ export function collectReferenceGuidance(input) {
17
+ const block = formatReferenceBlock(input.referencedStores);
18
+ return {
19
+ claude: block,
20
+ cursor: block,
21
+ codex: block,
22
+ };
23
+ }
2
24
  function runtimePrefix(ide) {
3
25
  if (ide === 'claude') {
4
26
  return '.claude/specflow/';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gordon.gan/specflow",
3
- "version": "1.1.1",
3
+ "version": "1.2.0-beta.1",
4
4
  "type": "module",
5
5
  "description": "SpecFlow — unified spec-driven development: OpenSpec planning + Superpowers execution in one CLI and cross-IDE workflow",
6
6
  "keywords": [
@@ -0,0 +1,30 @@
1
+ # Artifact Language Policy
2
+
3
+ Before creating or rewriting any SpecFlow artifact:
4
+
5
+ 1. Read `specflow/config.yaml` from the active planning root.
6
+ 2. Resolve `artifacts.language`:
7
+ - `en` → write human-readable artifact content in English.
8
+ - `zh-CN` → write human-readable artifact content in Simplified Chinese.
9
+ - Missing → default to `en`.
10
+ - Invalid → report the invalid value and use `en`; do not invent another language.
11
+ 3. Apply the selected language consistently to narrative prose, requirement and scenario descriptions, design rationale, and task descriptions.
12
+
13
+ ## Protected Protocol
14
+
15
+ Language selection never changes SpecFlow's machine-readable protocol. Keep these tokens exactly as written:
16
+
17
+ - `## ADDED Requirements`
18
+ - `## MODIFIED Requirements`
19
+ - `## REMOVED Requirements`
20
+ - `## RENAMED Requirements`
21
+ - `### Requirement:`
22
+ - `#### Scenario:`
23
+ - `- **WHEN**`
24
+ - `- **THEN**`
25
+ - `FROM:`
26
+ - `TO:`
27
+
28
+ Also preserve capability IDs, change names, file paths, commands, code, symbols, and established technical identifiers.
29
+
30
+ When modifying an existing requirement, preserve its exact requirement name if delta matching depends on that name. Do not translate historical artifacts automatically; the configured language applies prospectively to content created or intentionally rewritten in the current workflow.
@@ -5,15 +5,6 @@ description: "Two-phase apply — task rewrite + subagent TDD execution"
5
5
 
6
6
  # SpecFlow: Apply
7
7
 
8
- ## Invocation mode
9
-
10
- Inspect the current user invocation before any stage work:
11
-
12
- - **Default mode:** `/specflow:apply` preserves every existing user confirmation gate and the default interactive behavior.
13
- - **Yes mode:** `/specflow:apply --yes` records non-interactive confirmation mode for the current invocation only. It auto-accepts only successful acknowledgement gates; it never bypasses prerequisites, gap detection, failing verification, or a review `Block`.
14
-
15
- Report the selected mode once at the start of the session. Do not persist it to future invocations.
16
-
17
8
  > **HARD GATE (prerequisite)**: phase must be `refined`. Run /specflow:refine first if not.
18
9
  > **HARD GATE (Phase A)**: rewritten tasks.md must be user-confirmed before Phase B.
19
10
  > **HARD GATE (Phase B)**: each task must be reviewed (spec + code quality) and user-confirmed before next task.
@@ -27,6 +18,13 @@ Report the selected mode once at the start of the session. Do not persist it to
27
18
  - Existing `tasks.md` is present (coarse first-iteration from `/specflow:propose`, or a refine-updated version).
28
19
  - `specflow` CLI is available on PATH.
29
20
 
21
+ ## Artifact Language Setup
22
+
23
+ Before Phase A rewrites `tasks.md`, read
24
+ `.claude/specflow/prompts/shared/artifact-language.md` and resolve the active
25
+ planning root's `specflow/config.yaml`. Apply the policy to rewritten task
26
+ descriptions while preserving paths, commands, code, and symbols.
27
+
30
28
  ---
31
29
 
32
30
  ## Phase A: Task Rewrite
@@ -65,10 +63,7 @@ Present the audit summary to the user: per-group task count (coarse → atomic),
65
63
 
66
64
  ### Gate A: Rewrite Confirmation
67
65
 
68
- - **Default mode:** Present the rewritten `tasks.md` and ask the user to confirm the rewrite. Do NOT proceed to Phase B until the user explicitly confirms.
69
- - **Yes mode:** Present the rewrite audit and continue directly to Phase B after recording the rewrite as automatically accepted for this invocation.
70
-
71
- If Phase A reports a reorganization choice or a design gap, stop regardless of mode; `--yes` does not choose a reorganization or fill a missing design decision.
66
+ Present the rewritten `tasks.md` to the user. **Ask the user to confirm the rewrite.** Do NOT proceed to Phase B until the user explicitly confirms.
72
67
 
73
68
  ---
74
69
 
@@ -120,26 +115,16 @@ ECC reviewer verdict routing:
120
115
 
121
116
  #### Gate B: Per-task Confirmation
122
117
 
123
- - **Default mode:** Present the task output and both review reports. Ask the user to confirm the task is complete. Do NOT proceed to the next task until confirmation is received.
124
- - **Yes mode:** Present the task output and both review reports. If spec review passes and code-quality review returns `Approve` or `Warning`, record the task as automatically accepted and start the next task without waiting.
118
+ Present the task output and both review reports to the user. **Ask the user to confirm the task is complete.** Do NOT proceed to the next task until confirmation is received.
125
119
 
126
- If either review blocks, return to Stage B2a for the same task. `--yes` never advances past a blocked review.
120
+ If the user rejects, return to Stage B2a for the same task.
127
121
 
128
122
  ### Stage B3: Phase Transition
129
123
 
130
- Once all tasks are confirmed in default mode or automatically accepted in yes mode:
124
+ Once all tasks are confirmed:
131
125
  - Invoke `specflow change phase <name> --set apply` to transition the phase.
132
126
  - Summarize the build results and overall coverage.
133
-
134
- #### Yes-mode downstream sequence
135
-
136
- When yes mode reaches this point:
137
- 1. Invoke `/specflow:review`. Stop if it reports a CRITICAL or HIGH finding.
138
- 2. Invoke `/specflow:test` only after review has no CRITICAL/HIGH findings. Stop if tests cannot be made green.
139
- 3. Invoke `/specflow:verify` only after test succeeds. Stop if verify returns `FAIL`.
140
- 4. Report review, test, and verification evidence. Do NOT invoke `/specflow:archive` automatically; archive remains the user's manual choice.
141
-
142
- When default mode reaches this point, inform the user that they can now run `/specflow:review` or `/specflow:verify`.
127
+ - Inform the user they can now run `/specflow:review` or `/specflow:verify`.
143
128
 
144
129
  ---
145
130
 
@@ -33,6 +33,12 @@ Skip explore when you already know exactly what to build — run `/specflow:prop
33
33
  - `specflow/` exists (suggest `specflow init` if missing)
34
34
  - `specflow` CLI available on PATH
35
35
 
36
+ ## Artifact Language Setup
37
+
38
+ Read `.claude/specflow/prompts/shared/artifact-language.md` and resolve the active
39
+ planning root's `specflow/config.yaml` before writing or rewriting `explore.md`.
40
+ Apply that policy to every artifact created by this workflow.
41
+
36
42
  ## Stage 1: Create or Reuse Change
37
43
 
38
44
  If no active change directory exists for this work:
@@ -18,6 +18,12 @@ Treat every artifact here as "v1, to be iterated on" — depth matters, but so d
18
18
  - `specflow/specs/` directory should exist, indicating this is a specflow-initialized project. For a brand-new greenfield change with no existing specs, proceed — the prompt handles that case. If `specflow/` itself does not exist, suggest running `specflow init` first.
19
19
  - `specflow` CLI must be available on PATH.
20
20
 
21
+ ## Artifact Language Setup
22
+
23
+ Read `.claude/specflow/prompts/shared/artifact-language.md` and resolve the active
24
+ planning root's `specflow/config.yaml` before generating proposal, specs, design,
25
+ or tasks. Reuse the resolved policy for all four artifacts.
26
+
21
27
  ## Stage 0: Explore Handoff (when explore.md exists)
22
28
 
23
29
  Before creating a new change or generating a proposal, check for an existing exploration artifact:
@@ -26,6 +26,12 @@ emits a round diff summary.
26
26
 
27
27
  If any precondition fails, stop and instruct the user to run `/specflow:propose` first.
28
28
 
29
+ ## Artifact Language Setup
30
+
31
+ Read `.claude/specflow/prompts/shared/artifact-language.md` and resolve the active
32
+ planning root's `specflow/config.yaml` before refining any artifact. Preserve
33
+ existing requirement names where delta matching requires exact identity.
34
+
29
35
  ## Stage 1: Pre-loop setup
30
36
 
31
37
  Read, in order:
@@ -10,6 +10,12 @@ description: "Post-hoc change documentation from git diff"
10
10
  - Git repository must have uncommitted or recent committed changes to document.
11
11
  - `specflow` CLI must be available on PATH.
12
12
 
13
+ ## Artifact Language Setup
14
+
15
+ Read `.claude/specflow/prompts/shared/artifact-language.md` and resolve the active
16
+ planning root's `specflow/config.yaml` before synthesizing proposal, specs, or
17
+ tasks. Reuse the resolved policy for every generated artifact.
18
+
13
19
  ## Stage 1: Analyze Changes
14
20
 
15
21
  Run `git diff` and `git log` to capture what changed.