@fission-ai/openspec 1.3.1 → 1.4.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 (165) hide show
  1. package/README.md +9 -2
  2. package/bin/openspec.js +3 -1
  3. package/dist/cli/index.d.ts +4 -1
  4. package/dist/cli/index.js +65 -3
  5. package/dist/commands/completion.js +7 -0
  6. package/dist/commands/config.js +64 -5
  7. package/dist/commands/context-store.d.ts +3 -0
  8. package/dist/commands/context-store.js +475 -0
  9. package/dist/commands/initiative.d.ts +13 -0
  10. package/dist/commands/initiative.js +318 -0
  11. package/dist/commands/workflow/index.d.ts +2 -0
  12. package/dist/commands/workflow/index.js +1 -0
  13. package/dist/commands/workflow/initiative-link.d.ts +24 -0
  14. package/dist/commands/workflow/initiative-link.js +47 -0
  15. package/dist/commands/workflow/instructions.d.ts +1 -1
  16. package/dist/commands/workflow/instructions.js +28 -11
  17. package/dist/commands/workflow/new-change.d.ts +6 -0
  18. package/dist/commands/workflow/new-change.js +114 -17
  19. package/dist/commands/workflow/set-change.d.ts +13 -0
  20. package/dist/commands/workflow/set-change.js +87 -0
  21. package/dist/commands/workflow/shared.d.ts +4 -2
  22. package/dist/commands/workflow/shared.js +6 -6
  23. package/dist/commands/workflow/status.js +19 -4
  24. package/dist/commands/workspace/context-status.d.ts +4 -0
  25. package/dist/commands/workspace/context-status.js +59 -0
  26. package/dist/commands/workspace/open-target-selection.d.ts +13 -0
  27. package/dist/commands/workspace/open-target-selection.js +146 -0
  28. package/dist/commands/workspace/open-view.d.ts +62 -0
  29. package/dist/commands/workspace/open-view.js +249 -0
  30. package/dist/commands/workspace/open.d.ts +37 -0
  31. package/dist/commands/workspace/open.js +110 -0
  32. package/dist/commands/workspace/opener-selection.d.ts +11 -0
  33. package/dist/commands/workspace/opener-selection.js +98 -0
  34. package/dist/commands/workspace/operations.d.ts +29 -0
  35. package/dist/commands/workspace/operations.js +543 -0
  36. package/dist/commands/workspace/prompt-theme.d.ts +29 -0
  37. package/dist/commands/workspace/prompt-theme.js +24 -0
  38. package/dist/commands/workspace/registration.d.ts +13 -0
  39. package/dist/commands/workspace/registration.js +84 -0
  40. package/dist/commands/workspace/selection.d.ts +8 -0
  41. package/dist/commands/workspace/selection.js +104 -0
  42. package/dist/commands/workspace/setup-prompts.d.ts +13 -0
  43. package/dist/commands/workspace/setup-prompts.js +121 -0
  44. package/dist/commands/workspace/types.d.ts +103 -0
  45. package/dist/commands/workspace/types.js +36 -0
  46. package/dist/commands/workspace.d.ts +5 -0
  47. package/dist/commands/workspace.js +574 -0
  48. package/dist/core/artifact-graph/index.d.ts +2 -1
  49. package/dist/core/artifact-graph/instruction-loader.d.ts +41 -1
  50. package/dist/core/artifact-graph/instruction-loader.js +46 -6
  51. package/dist/core/artifact-graph/types.d.ts +0 -5
  52. package/dist/core/artifact-graph/types.js +0 -14
  53. package/dist/core/change-metadata/index.d.ts +2 -0
  54. package/dist/core/change-metadata/index.js +2 -0
  55. package/dist/core/change-metadata/schema.d.ts +18 -0
  56. package/dist/core/change-metadata/schema.js +28 -0
  57. package/dist/core/change-status-policy.d.ts +50 -0
  58. package/dist/core/change-status-policy.js +70 -0
  59. package/dist/core/collections/index.d.ts +3 -0
  60. package/dist/core/collections/index.js +3 -0
  61. package/dist/core/collections/initiatives/collection.d.ts +4 -0
  62. package/dist/core/collections/initiatives/collection.js +17 -0
  63. package/dist/core/collections/initiatives/index.d.ts +6 -0
  64. package/dist/core/collections/initiatives/index.js +6 -0
  65. package/dist/core/collections/initiatives/operations.d.ts +49 -0
  66. package/dist/core/collections/initiatives/operations.js +175 -0
  67. package/dist/core/collections/initiatives/resolution.d.ts +87 -0
  68. package/dist/core/collections/initiatives/resolution.js +374 -0
  69. package/dist/core/collections/initiatives/schema.d.ts +41 -0
  70. package/dist/core/collections/initiatives/schema.js +134 -0
  71. package/dist/core/collections/initiatives/templates.d.ts +12 -0
  72. package/dist/core/collections/initiatives/templates.js +90 -0
  73. package/dist/core/collections/runtime.d.ts +46 -0
  74. package/dist/core/collections/runtime.js +194 -0
  75. package/dist/core/completions/command-registry.d.ts +1 -5
  76. package/dist/core/completions/command-registry.js +532 -32
  77. package/dist/core/completions/completion-provider.d.ts +11 -0
  78. package/dist/core/completions/completion-provider.js +27 -0
  79. package/dist/core/completions/generators/bash-generator.d.ts +3 -0
  80. package/dist/core/completions/generators/bash-generator.js +60 -4
  81. package/dist/core/completions/generators/fish-generator.js +3 -0
  82. package/dist/core/completions/generators/powershell-generator.d.ts +3 -0
  83. package/dist/core/completions/generators/powershell-generator.js +63 -4
  84. package/dist/core/completions/generators/zsh-generator.d.ts +3 -0
  85. package/dist/core/completions/generators/zsh-generator.js +46 -22
  86. package/dist/core/completions/installers/zsh-installer.d.ts +0 -8
  87. package/dist/core/completions/installers/zsh-installer.js +3 -31
  88. package/dist/core/completions/shared-flags.d.ts +12 -0
  89. package/dist/core/completions/shared-flags.js +28 -0
  90. package/dist/core/completions/templates/bash-templates.d.ts +1 -1
  91. package/dist/core/completions/templates/bash-templates.js +6 -0
  92. package/dist/core/completions/templates/fish-templates.d.ts +1 -1
  93. package/dist/core/completions/templates/fish-templates.js +6 -0
  94. package/dist/core/completions/templates/powershell-templates.d.ts +1 -1
  95. package/dist/core/completions/templates/powershell-templates.js +9 -0
  96. package/dist/core/completions/templates/zsh-templates.d.ts +1 -1
  97. package/dist/core/completions/templates/zsh-templates.js +9 -0
  98. package/dist/core/completions/types.d.ts +23 -1
  99. package/dist/core/config.js +3 -1
  100. package/dist/core/context-store/binding.d.ts +53 -0
  101. package/dist/core/context-store/binding.js +197 -0
  102. package/dist/core/context-store/errors.d.ts +20 -0
  103. package/dist/core/context-store/errors.js +22 -0
  104. package/dist/core/context-store/foundation.d.ts +55 -0
  105. package/dist/core/context-store/foundation.js +321 -0
  106. package/dist/core/context-store/index.d.ts +6 -0
  107. package/dist/core/context-store/index.js +6 -0
  108. package/dist/core/context-store/operations.d.ts +85 -0
  109. package/dist/core/context-store/operations.js +528 -0
  110. package/dist/core/context-store/registry.d.ts +45 -0
  111. package/dist/core/context-store/registry.js +229 -0
  112. package/dist/core/global-config.d.ts +6 -1
  113. package/dist/core/global-config.js +15 -16
  114. package/dist/core/index.d.ts +5 -1
  115. package/dist/core/index.js +4 -0
  116. package/dist/core/parsers/requirement-blocks.js +5 -5
  117. package/dist/core/parsers/spec-structure.js +1 -1
  118. package/dist/core/planning-home.d.ts +21 -0
  119. package/dist/core/planning-home.js +108 -0
  120. package/dist/core/profiles.d.ts +1 -1
  121. package/dist/core/profiles.js +1 -1
  122. package/dist/core/specs-apply.js +1 -1
  123. package/dist/core/templates/workflows/apply-change.js +6 -0
  124. package/dist/core/templates/workflows/archive-change.js +21 -15
  125. package/dist/core/templates/workflows/bulk-archive-change.js +14 -10
  126. package/dist/core/templates/workflows/continue-change.js +6 -4
  127. package/dist/core/templates/workflows/explore.js +8 -10
  128. package/dist/core/templates/workflows/ff-change.js +8 -6
  129. package/dist/core/templates/workflows/new-change.js +6 -6
  130. package/dist/core/templates/workflows/onboard.js +13 -15
  131. package/dist/core/templates/workflows/propose.js +8 -6
  132. package/dist/core/templates/workflows/sync-specs.js +28 -10
  133. package/dist/core/templates/workflows/verify-change.js +10 -4
  134. package/dist/core/update.d.ts +5 -0
  135. package/dist/core/update.js +20 -0
  136. package/dist/core/validation/validator.d.ts +11 -0
  137. package/dist/core/validation/validator.js +19 -2
  138. package/dist/core/workspace/foundation.d.ts +67 -0
  139. package/dist/core/workspace/foundation.js +295 -0
  140. package/dist/core/workspace/index.d.ts +8 -0
  141. package/dist/core/workspace/index.js +8 -0
  142. package/dist/core/workspace/legacy-state.d.ts +28 -0
  143. package/dist/core/workspace/legacy-state.js +200 -0
  144. package/dist/core/workspace/link-input.d.ts +9 -0
  145. package/dist/core/workspace/link-input.js +32 -0
  146. package/dist/core/workspace/open-surface.d.ts +45 -0
  147. package/dist/core/workspace/open-surface.js +215 -0
  148. package/dist/core/workspace/openers.d.ts +21 -0
  149. package/dist/core/workspace/openers.js +124 -0
  150. package/dist/core/workspace/registry.d.ts +24 -0
  151. package/dist/core/workspace/registry.js +146 -0
  152. package/dist/core/workspace/skills.d.ts +57 -0
  153. package/dist/core/workspace/skills.js +334 -0
  154. package/dist/core/workspace/state-io.d.ts +10 -0
  155. package/dist/core/workspace/state-io.js +121 -0
  156. package/dist/utils/change-metadata.d.ts +5 -2
  157. package/dist/utils/change-metadata.js +7 -13
  158. package/dist/utils/change-utils.d.ts +9 -0
  159. package/dist/utils/change-utils.js +7 -5
  160. package/package.json +2 -1
  161. package/schemas/workspace-planning/schema.yaml +72 -0
  162. package/schemas/workspace-planning/templates/design.md +33 -0
  163. package/schemas/workspace-planning/templates/proposal.md +28 -0
  164. package/schemas/workspace-planning/templates/spec.md +9 -0
  165. package/schemas/workspace-planning/templates/tasks.md +15 -0
@@ -3,8 +3,10 @@ import * as path from 'node:path';
3
3
  import { getSchemaDir, resolveSchema } from './resolver.js';
4
4
  import { ArtifactGraph } from './graph.js';
5
5
  import { detectCompleted } from './state.js';
6
- import { resolveSchemaForChange } from '../../utils/change-metadata.js';
6
+ import { resolveArtifactOutputs } from './outputs.js';
7
+ import { readChangeMetadata, resolveSchemaForChange } from '../../utils/change-metadata.js';
7
8
  import { FileSystemUtils } from '../../utils/file-system.js';
9
+ import { buildActionContext, buildNextSteps, summarizeAffectedAreas, summarizePlanningHome, } from '../change-status-policy.js';
8
10
  import { readProjectConfig, validateConfigRules } from '../project-config.js';
9
11
  // Session-level cache for validation warnings (avoid repeating same warnings)
10
12
  const shownWarnings = new Set();
@@ -59,10 +61,12 @@ export function loadTemplate(schemaName, templatePath, projectRoot) {
59
61
  * @param schemaName - Optional schema name override. If not provided, auto-detected from metadata.
60
62
  * @returns Change context with graph, completed set, and metadata
61
63
  */
62
- export function loadChangeContext(projectRoot, changeName, schemaName) {
63
- const changeDir = FileSystemUtils.canonicalizeExistingPath(path.join(projectRoot, 'openspec', 'changes', changeName));
64
- // Resolve schema: explicit > metadata > default
65
- const resolvedSchemaName = resolveSchemaForChange(changeDir, schemaName);
64
+ export function loadChangeContext(projectRoot, changeName, schemaName, options = {}) {
65
+ const changeDir = FileSystemUtils.canonicalizeExistingPath(options.changeDir ?? path.join(projectRoot, 'openspec', 'changes', changeName));
66
+ const metadata = readChangeMetadata(changeDir, projectRoot) ?? undefined;
67
+ const resolvedSchemaName = resolveSchemaForChange(changeDir, schemaName, projectRoot, {
68
+ metadata: metadata ?? null,
69
+ });
66
70
  const schema = resolveSchema(resolvedSchemaName, projectRoot);
67
71
  const graph = ArtifactGraph.fromSchema(schema);
68
72
  const completed = detectCompleted(graph, changeDir);
@@ -73,6 +77,9 @@ export function loadChangeContext(projectRoot, changeName, schemaName) {
73
77
  changeName,
74
78
  changeDir,
75
79
  projectRoot,
80
+ ...(options.planningHome ? { planningHome: options.planningHome } : {}),
81
+ ...(metadata ? { metadata } : {}),
82
+ ...(metadata?.initiative ? { initiative: metadata.initiative } : {}),
76
83
  };
77
84
  }
78
85
  /**
@@ -130,7 +137,11 @@ export function generateInstructions(context, artifactId, projectRoot) {
130
137
  artifactId: artifact.id,
131
138
  schemaName: context.schemaName,
132
139
  changeDir: context.changeDir,
140
+ planningHome: summarizePlanningHome(context.planningHome),
141
+ ...(context.initiative ? { initiative: context.initiative } : {}),
133
142
  outputPath: artifact.generates,
143
+ resolvedOutputPath: path.join(context.changeDir, artifact.generates),
144
+ existingOutputPaths: resolveArtifactOutputs(context.changeDir, artifact.generates),
134
145
  description: artifact.description,
135
146
  instruction: artifact.instruction,
136
147
  context: configContext,
@@ -179,7 +190,13 @@ export function formatChangeStatus(context) {
179
190
  const artifacts = context.graph.getAllArtifacts();
180
191
  const ready = new Set(context.graph.getNextArtifacts(context.completed));
181
192
  const blocked = context.graph.getBlocked(context.completed);
193
+ const artifactPaths = {};
182
194
  const artifactStatuses = artifacts.map(artifact => {
195
+ artifactPaths[artifact.id] = {
196
+ outputPath: artifact.generates,
197
+ resolvedOutputPath: path.join(context.changeDir, artifact.generates),
198
+ existingOutputPaths: resolveArtifactOutputs(context.changeDir, artifact.generates),
199
+ };
183
200
  if (context.completed.has(artifact.id)) {
184
201
  return {
185
202
  id: artifact.id,
@@ -205,11 +222,34 @@ export function formatChangeStatus(context) {
205
222
  const buildOrder = context.graph.getBuildOrder();
206
223
  const orderMap = new Map(buildOrder.map((id, idx) => [id, idx]));
207
224
  artifactStatuses.sort((a, b) => (orderMap.get(a.id) ?? 0) - (orderMap.get(b.id) ?? 0));
225
+ const affectedAreas = summarizeAffectedAreas({
226
+ planningHome: context.planningHome,
227
+ metadata: context.metadata,
228
+ });
229
+ const isComplete = context.graph.isComplete(context.completed);
230
+ const artifactIds = artifactStatuses.map((artifact) => artifact.id);
208
231
  return {
209
232
  changeName: context.changeName,
210
233
  schemaName: context.schemaName,
211
- isComplete: context.graph.isComplete(context.completed),
234
+ planningHome: summarizePlanningHome(context.planningHome),
235
+ ...(context.initiative ? { initiative: context.initiative } : {}),
236
+ changeRoot: context.changeDir,
237
+ artifactPaths,
238
+ affectedAreas,
239
+ isComplete,
212
240
  applyRequires,
241
+ nextSteps: buildNextSteps({
242
+ changeName: context.changeName,
243
+ planningHome: context.planningHome,
244
+ artifactStatuses,
245
+ affectedAreas,
246
+ allArtifactsComplete: isComplete,
247
+ }),
248
+ actionContext: buildActionContext({
249
+ planningHome: context.planningHome,
250
+ projectRoot: context.projectRoot,
251
+ artifactIds,
252
+ }),
213
253
  artifacts: artifactStatuses,
214
254
  };
215
255
  }
@@ -33,11 +33,6 @@ export declare const SchemaYamlSchema: z.ZodObject<{
33
33
  export type Artifact = z.infer<typeof ArtifactSchema>;
34
34
  export type ApplyPhase = z.infer<typeof ApplyPhaseSchema>;
35
35
  export type SchemaYaml = z.infer<typeof SchemaYamlSchema>;
36
- export declare const ChangeMetadataSchema: z.ZodObject<{
37
- schema: z.ZodString;
38
- created: z.ZodOptional<z.ZodString>;
39
- }, z.core.$strip>;
40
- export type ChangeMetadata = z.infer<typeof ChangeMetadataSchema>;
41
36
  export type CompletedSet = Set<string>;
42
37
  export interface BlockedArtifacts {
43
38
  [artifactId: string]: string[];
@@ -26,18 +26,4 @@ export const SchemaYamlSchema = z.object({
26
26
  // Optional apply phase configuration (for schema-aware apply instructions)
27
27
  apply: ApplyPhaseSchema.optional(),
28
28
  });
29
- // Per-change metadata schema
30
- // Note: schema field is validated at parse time against available schemas
31
- // using a lazy import to avoid circular dependencies
32
- export const ChangeMetadataSchema = z.object({
33
- // Required: which workflow schema this change uses
34
- schema: z.string().min(1, { message: 'schema is required' }),
35
- // Optional: creation timestamp (ISO date string)
36
- created: z
37
- .string()
38
- .regex(/^\d{4}-\d{2}-\d{2}$/, {
39
- message: 'created must be YYYY-MM-DD format',
40
- })
41
- .optional(),
42
- });
43
29
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,2 @@
1
+ export * from './schema.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from './schema.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ export declare const InitiativeLinkSchema: z.ZodObject<{
3
+ store: z.ZodString;
4
+ id: z.ZodString;
5
+ }, z.core.$strict>;
6
+ export type InitiativeLink = z.infer<typeof InitiativeLinkSchema>;
7
+ export declare const ChangeMetadataSchema: z.ZodObject<{
8
+ schema: z.ZodString;
9
+ created: z.ZodOptional<z.ZodString>;
10
+ goal: z.ZodOptional<z.ZodString>;
11
+ affected_areas: z.ZodOptional<z.ZodArray<z.ZodString>>;
12
+ initiative: z.ZodOptional<z.ZodObject<{
13
+ store: z.ZodString;
14
+ id: z.ZodString;
15
+ }, z.core.$strict>>;
16
+ }, z.core.$strip>;
17
+ export type ChangeMetadata = z.infer<typeof ChangeMetadataSchema>;
18
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ const KebabIdentifierSchema = (label) => z.string().superRefine((value, ctx) => {
3
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value)) {
4
+ ctx.addIssue({
5
+ code: 'custom',
6
+ message: `${label} must be kebab-case with lowercase letters, numbers, and single hyphen separators`,
7
+ });
8
+ }
9
+ });
10
+ export const InitiativeLinkSchema = z.object({
11
+ store: KebabIdentifierSchema('Context store id'),
12
+ id: KebabIdentifierSchema('Initiative id'),
13
+ }).strict();
14
+ // Per-change metadata schema. The schema field is validated against available
15
+ // workflow schemas when metadata is read or written.
16
+ export const ChangeMetadataSchema = z.object({
17
+ schema: z.string().min(1, { message: 'schema is required' }),
18
+ created: z
19
+ .string()
20
+ .regex(/^\d{4}-\d{2}-\d{2}$/, {
21
+ message: 'created must be YYYY-MM-DD format',
22
+ })
23
+ .optional(),
24
+ goal: z.string().min(1).optional(),
25
+ affected_areas: z.array(z.string().min(1)).optional(),
26
+ initiative: InitiativeLinkSchema.optional(),
27
+ });
28
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,50 @@
1
+ import type { ChangeMetadata } from './change-metadata/index.js';
2
+ import type { PlanningHome } from './planning-home.js';
3
+ export interface PlanningHomeSummary {
4
+ kind: 'repo' | 'workspace';
5
+ root: string;
6
+ changesDir: string;
7
+ defaultSchema: string;
8
+ workspaceName?: string;
9
+ }
10
+ export interface AffectedAreasSummary {
11
+ known: string[];
12
+ unresolved: boolean;
13
+ invalid: string[];
14
+ }
15
+ export interface ActionContext {
16
+ mode: 'repo-local' | 'workspace-planning';
17
+ sourceOfTruth: 'repo' | 'workspace-local';
18
+ planningArtifacts: string[];
19
+ linkedContext: Array<{
20
+ name: string;
21
+ }>;
22
+ allowedEditRoots: string[];
23
+ requiresAffectedAreaSelection: boolean;
24
+ constraints: string[];
25
+ }
26
+ export interface ChangeStatusPolicyArtifact {
27
+ id: string;
28
+ status: 'done' | 'ready' | 'blocked';
29
+ }
30
+ export interface AffectedAreasInput {
31
+ planningHome?: PlanningHome;
32
+ metadata?: ChangeMetadata;
33
+ }
34
+ export interface ChangeNextStepsInput {
35
+ changeName: string;
36
+ planningHome?: PlanningHome;
37
+ artifactStatuses: ChangeStatusPolicyArtifact[];
38
+ affectedAreas?: AffectedAreasSummary;
39
+ allArtifactsComplete: boolean;
40
+ }
41
+ export interface ActionContextInput {
42
+ planningHome?: PlanningHome;
43
+ projectRoot: string;
44
+ artifactIds: string[];
45
+ }
46
+ export declare function summarizePlanningHome(planningHome: PlanningHome | undefined): PlanningHomeSummary | undefined;
47
+ export declare function summarizeAffectedAreas(input: AffectedAreasInput): AffectedAreasSummary | undefined;
48
+ export declare function buildActionContext(input: ActionContextInput): ActionContext;
49
+ export declare function buildNextSteps(input: ChangeNextStepsInput): string[];
50
+ //# sourceMappingURL=change-status-policy.d.ts.map
@@ -0,0 +1,70 @@
1
+ export function summarizePlanningHome(planningHome) {
2
+ if (!planningHome) {
3
+ return undefined;
4
+ }
5
+ return {
6
+ kind: planningHome.kind,
7
+ root: planningHome.root,
8
+ changesDir: planningHome.changesDir,
9
+ defaultSchema: planningHome.defaultSchema,
10
+ ...(planningHome.workspace ? { workspaceName: planningHome.workspace.name } : {}),
11
+ };
12
+ }
13
+ export function summarizeAffectedAreas(input) {
14
+ if (input.planningHome?.kind !== 'workspace') {
15
+ return undefined;
16
+ }
17
+ const known = Array.from(new Set(input.metadata?.affected_areas ?? [])).sort((a, b) => a.localeCompare(b));
18
+ const validAreas = new Set(input.planningHome.workspace?.links ?? []);
19
+ const invalid = known.filter((areaName) => validAreas.size > 0 && !validAreas.has(areaName));
20
+ return {
21
+ known,
22
+ unresolved: known.length === 0,
23
+ invalid,
24
+ };
25
+ }
26
+ export function buildActionContext(input) {
27
+ if (input.planningHome?.kind === 'workspace') {
28
+ return {
29
+ mode: 'workspace-planning',
30
+ sourceOfTruth: 'workspace-local',
31
+ planningArtifacts: input.artifactIds,
32
+ linkedContext: (input.planningHome.workspace?.links ?? []).map((name) => ({ name })),
33
+ allowedEditRoots: [],
34
+ requiresAffectedAreaSelection: true,
35
+ constraints: [
36
+ 'Treat workspace-local planning artifacts as compatibility context for this local view.',
37
+ 'Use initiatives for durable coordination when initiative context exists.',
38
+ 'Treat linked repos and folders as context until an explicit edit root is selected.',
39
+ 'Do not make implementation edits without an explicit allowed edit root.',
40
+ ],
41
+ };
42
+ }
43
+ return {
44
+ mode: 'repo-local',
45
+ sourceOfTruth: 'repo',
46
+ planningArtifacts: input.artifactIds,
47
+ linkedContext: [],
48
+ allowedEditRoots: [input.projectRoot],
49
+ requiresAffectedAreaSelection: false,
50
+ constraints: ['Repo-local change artifacts and implementation edits are scoped to this project.'],
51
+ };
52
+ }
53
+ export function buildNextSteps(input) {
54
+ const readyArtifact = input.artifactStatuses.find((artifact) => artifact.status === 'ready');
55
+ const steps = [];
56
+ if (readyArtifact) {
57
+ steps.push(`Run openspec instructions ${readyArtifact.id} --change "${input.changeName}" --json before writing that artifact.`);
58
+ }
59
+ else if (input.allArtifactsComplete) {
60
+ steps.push('All planning artifacts are complete; review tasks before implementation.');
61
+ }
62
+ if (input.planningHome?.kind === 'workspace') {
63
+ if (input.affectedAreas?.unresolved) {
64
+ steps.push('Identify affected areas in change metadata or coordination tasks as planning continues.');
65
+ }
66
+ steps.push('Select an affected area and allowed edit root before implementation edits.');
67
+ }
68
+ return steps;
69
+ }
70
+ //# sourceMappingURL=change-status-policy.js.map
@@ -0,0 +1,3 @@
1
+ export * from './runtime.js';
2
+ export * from './initiatives/index.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export * from './runtime.js';
2
+ export * from './initiatives/index.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,4 @@
1
+ import { type CollectionRegistry, type MountedCollection } from '../runtime.js';
2
+ export declare function createInitiativesCollectionRegistry(): CollectionRegistry;
3
+ export declare function mountInitiativesCollection(storeRoot: string): MountedCollection;
4
+ //# sourceMappingURL=collection.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { createCollectionRegistry, mountCollections, } from '../runtime.js';
2
+ import { INITIATIVE_COLLECTION_ID } from './schema.js';
3
+ export function createInitiativesCollectionRegistry() {
4
+ return createCollectionRegistry([
5
+ {
6
+ id: INITIATIVE_COLLECTION_ID,
7
+ mount: INITIATIVE_COLLECTION_ID,
8
+ },
9
+ ]);
10
+ }
11
+ export function mountInitiativesCollection(storeRoot) {
12
+ return mountCollections({
13
+ storeRoot,
14
+ collections: createInitiativesCollectionRegistry(),
15
+ }).require(INITIATIVE_COLLECTION_ID);
16
+ }
17
+ //# sourceMappingURL=collection.js.map
@@ -0,0 +1,6 @@
1
+ export * from './collection.js';
2
+ export * from './schema.js';
3
+ export * from './templates.js';
4
+ export * from './operations.js';
5
+ export * from './resolution.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ export * from './collection.js';
2
+ export * from './schema.js';
3
+ export * from './templates.js';
4
+ export * from './operations.js';
5
+ export * from './resolution.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,49 @@
1
+ import * as nodeFs from 'node:fs';
2
+ import type { MountedCollection } from '../runtime.js';
3
+ import { type InitiativeMetadata, type InitiativeState, type InitiativeStatus } from './schema.js';
4
+ import { type InitiativeTemplateFile } from './templates.js';
5
+ export interface InitiativeDirectoryEntry {
6
+ name: string;
7
+ isDirectory(): boolean;
8
+ }
9
+ export interface InitiativeOperationsFileSystem {
10
+ mkdir(dirPath: string, options: {
11
+ recursive?: boolean;
12
+ }): Promise<void>;
13
+ writeFile(filePath: string, content: string, options: {
14
+ flag?: nodeFs.OpenMode;
15
+ }): Promise<void>;
16
+ readFile(filePath: string): Promise<string>;
17
+ readdir(dirPath: string, options: {
18
+ withFileTypes: true;
19
+ }): Promise<readonly InitiativeDirectoryEntry[]>;
20
+ rm(dirPath: string, options: {
21
+ recursive?: boolean;
22
+ force?: boolean;
23
+ }): Promise<void>;
24
+ }
25
+ export interface InitiativeOperationDependencies {
26
+ fileSystem?: InitiativeOperationsFileSystem;
27
+ }
28
+ export interface CreateInitiativeInput extends InitiativeOperationDependencies {
29
+ collection: MountedCollection;
30
+ id: string;
31
+ title: string;
32
+ summary: string;
33
+ status?: InitiativeStatus;
34
+ owners?: string[];
35
+ metadata?: InitiativeMetadata;
36
+ getCurrentDate?: () => string;
37
+ buildTemplateFiles?: (state: InitiativeState) => readonly InitiativeTemplateFile[];
38
+ }
39
+ export interface ListInitiativesInput extends InitiativeOperationDependencies {
40
+ collection: MountedCollection;
41
+ }
42
+ export interface ReadInitiativeInput extends InitiativeOperationDependencies {
43
+ collection: MountedCollection;
44
+ id: string;
45
+ }
46
+ export declare function createInitiative(input: CreateInitiativeInput): Promise<InitiativeState>;
47
+ export declare function readInitiative(input: ReadInitiativeInput): Promise<InitiativeState | null>;
48
+ export declare function listInitiatives(input: ListInitiativesInput): Promise<InitiativeState[]>;
49
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1,175 @@
1
+ import * as nodeFs from 'node:fs';
2
+ import { INITIATIVE_COLLECTION_ID, INITIATIVE_FILE_NAME, parseInitiativeState, serializeInitiativeState, validateInitiativeId, } from './schema.js';
3
+ import { buildDefaultInitiativeFiles, } from './templates.js';
4
+ const fs = nodeFs.promises;
5
+ const nodeFileSystem = {
6
+ async mkdir(dirPath, options) {
7
+ await fs.mkdir(dirPath, options);
8
+ },
9
+ async writeFile(filePath, content, options) {
10
+ await fs.writeFile(filePath, content, {
11
+ encoding: 'utf-8',
12
+ flag: options.flag ?? 'w',
13
+ });
14
+ },
15
+ async readFile(filePath) {
16
+ return fs.readFile(filePath, 'utf-8');
17
+ },
18
+ async readdir(dirPath, options) {
19
+ return fs.readdir(dirPath, options);
20
+ },
21
+ async rm(dirPath, options) {
22
+ await fs.rm(dirPath, options);
23
+ },
24
+ };
25
+ function getCurrentDate() {
26
+ return new Date().toISOString().split('T')[0];
27
+ }
28
+ function getFileSystem(fileSystem) {
29
+ return fileSystem ?? nodeFileSystem;
30
+ }
31
+ function isFileNotFoundError(error) {
32
+ return (typeof error === 'object' &&
33
+ error !== null &&
34
+ 'code' in error &&
35
+ error.code === 'ENOENT');
36
+ }
37
+ function isPathExistsError(error) {
38
+ return (typeof error === 'object' &&
39
+ error !== null &&
40
+ 'code' in error &&
41
+ error.code === 'EEXIST');
42
+ }
43
+ function errorMessage(error) {
44
+ return error instanceof Error ? error.message : String(error);
45
+ }
46
+ function assertInitiativesCollection(collection) {
47
+ if (collection.collectionId !== INITIATIVE_COLLECTION_ID) {
48
+ throw new Error(`Expected mounted '${INITIATIVE_COLLECTION_ID}' collection, got '${collection.collectionId}'`);
49
+ }
50
+ }
51
+ function resolveInitiativeFilePath(collection, initiativeId, fileName) {
52
+ return collection.resolvePath(`${initiativeId}/${fileName}`);
53
+ }
54
+ function normalizeCreateState(input) {
55
+ return parseInitiativeState(serializeInitiativeState({
56
+ version: 1,
57
+ id: validateInitiativeId(input.id),
58
+ title: input.title,
59
+ summary: input.summary,
60
+ status: input.status ?? 'exploring',
61
+ created: (input.getCurrentDate ?? getCurrentDate)(),
62
+ owners: input.owners ?? [],
63
+ metadata: input.metadata ?? {},
64
+ }));
65
+ }
66
+ async function writeExclusiveFile(fileSystem, filePath, content) {
67
+ await fileSystem.writeFile(filePath, content, { flag: 'wx' });
68
+ }
69
+ async function cleanupCreatedInitiative(fileSystem, initiativeRoot, originalError, initiativeId) {
70
+ try {
71
+ await fileSystem.rm(initiativeRoot, { recursive: true, force: true });
72
+ }
73
+ catch (cleanupError) {
74
+ throw new Error(`Failed to create initiative '${initiativeId}' and cleanup failed: ${errorMessage(originalError)}; cleanup: ${errorMessage(cleanupError)}`);
75
+ }
76
+ throw new Error(`Failed to create initiative '${initiativeId}': ${errorMessage(originalError)}`);
77
+ }
78
+ export async function createInitiative(input) {
79
+ assertInitiativesCollection(input.collection);
80
+ const state = normalizeCreateState(input);
81
+ const fileSystem = getFileSystem(input.fileSystem);
82
+ const initiativeRoot = input.collection.resolvePath(state.id);
83
+ const buildTemplateFiles = input.buildTemplateFiles ?? buildDefaultInitiativeFiles;
84
+ try {
85
+ await fileSystem.mkdir(input.collection.resolvePath(), { recursive: true });
86
+ await fileSystem.mkdir(initiativeRoot, { recursive: false });
87
+ }
88
+ catch (error) {
89
+ if (isPathExistsError(error)) {
90
+ throw new Error(`Initiative '${state.id}' already exists at ${initiativeRoot}`);
91
+ }
92
+ throw new Error(`Failed to create initiative '${state.id}': ${errorMessage(error)}`);
93
+ }
94
+ try {
95
+ await writeExclusiveFile(fileSystem, resolveInitiativeFilePath(input.collection, state.id, INITIATIVE_FILE_NAME), serializeInitiativeState(state));
96
+ for (const templateFile of buildTemplateFiles(state)) {
97
+ await writeExclusiveFile(fileSystem, resolveInitiativeFilePath(input.collection, state.id, templateFile.fileName), templateFile.content);
98
+ }
99
+ }
100
+ catch (error) {
101
+ await cleanupCreatedInitiative(fileSystem, initiativeRoot, error, state.id);
102
+ }
103
+ return state;
104
+ }
105
+ export async function readInitiative(input) {
106
+ assertInitiativesCollection(input.collection);
107
+ const initiativeId = validateInitiativeId(input.id);
108
+ const fileSystem = getFileSystem(input.fileSystem);
109
+ const initiativeFilePath = resolveInitiativeFilePath(input.collection, initiativeId, INITIATIVE_FILE_NAME);
110
+ let content;
111
+ try {
112
+ content = await fileSystem.readFile(initiativeFilePath);
113
+ }
114
+ catch (error) {
115
+ if (isFileNotFoundError(error)) {
116
+ return null;
117
+ }
118
+ throw new Error(`Invalid initiative '${initiativeId}': failed to read ${INITIATIVE_FILE_NAME}: ${errorMessage(error)}`);
119
+ }
120
+ let state;
121
+ try {
122
+ state = parseInitiativeState(content);
123
+ }
124
+ catch (error) {
125
+ throw new Error(`Invalid initiative '${initiativeId}': ${errorMessage(error)}`);
126
+ }
127
+ if (state.id !== initiativeId) {
128
+ throw new Error(`Invalid initiative '${initiativeId}': ${INITIATIVE_FILE_NAME} id '${state.id}' must match folder name`);
129
+ }
130
+ return state;
131
+ }
132
+ export async function listInitiatives(input) {
133
+ assertInitiativesCollection(input.collection);
134
+ const fileSystem = getFileSystem(input.fileSystem);
135
+ let entries;
136
+ try {
137
+ entries = await fileSystem.readdir(input.collection.resolvePath(), { withFileTypes: true });
138
+ }
139
+ catch (error) {
140
+ if (isFileNotFoundError(error)) {
141
+ return [];
142
+ }
143
+ throw new Error(`Failed to list initiatives: ${errorMessage(error)}`);
144
+ }
145
+ const initiatives = [];
146
+ for (const entry of entries) {
147
+ if (!entry.isDirectory()) {
148
+ continue;
149
+ }
150
+ const initiativeFilePath = resolveInitiativeFilePath(input.collection, entry.name, INITIATIVE_FILE_NAME);
151
+ let content;
152
+ try {
153
+ content = await fileSystem.readFile(initiativeFilePath);
154
+ }
155
+ catch (error) {
156
+ if (isFileNotFoundError(error)) {
157
+ continue;
158
+ }
159
+ throw new Error(`Invalid initiative '${entry.name}': failed to read ${INITIATIVE_FILE_NAME}: ${errorMessage(error)}`);
160
+ }
161
+ let state;
162
+ try {
163
+ state = parseInitiativeState(content);
164
+ }
165
+ catch (error) {
166
+ throw new Error(`Invalid initiative '${entry.name}': ${errorMessage(error)}`);
167
+ }
168
+ if (state.id !== entry.name) {
169
+ throw new Error(`Invalid initiative '${entry.name}': ${INITIATIVE_FILE_NAME} id '${state.id}' must match folder name`);
170
+ }
171
+ initiatives.push(state);
172
+ }
173
+ return initiatives.sort((a, b) => a.id.localeCompare(b.id));
174
+ }
175
+ //# sourceMappingURL=operations.js.map
@@ -0,0 +1,87 @@
1
+ import { formatContextStoreSelector, type ContextStoreSelectorOptions, type ContextStoreSelectorSource, type SelectedContextStore } from '../../context-store/index.js';
2
+ import { type InitiativeState } from './schema.js';
3
+ export interface InitiativeSelectorOptions extends ContextStoreSelectorOptions {
4
+ json?: boolean;
5
+ }
6
+ export type { ContextStoreSelectorSource, SelectedContextStore };
7
+ export { formatContextStoreSelector };
8
+ export interface InitiativeResolutionMatch {
9
+ context_store: {
10
+ id: string;
11
+ root: string;
12
+ };
13
+ initiative: {
14
+ id: string;
15
+ title: string;
16
+ root: string;
17
+ };
18
+ }
19
+ export interface InitiativeResolutionDetails extends Record<string, unknown> {
20
+ matches?: InitiativeResolutionMatch[];
21
+ }
22
+ export declare class InitiativeResolutionError extends Error {
23
+ readonly code: string;
24
+ readonly target?: string;
25
+ readonly fix?: string;
26
+ readonly details?: InitiativeResolutionDetails;
27
+ constructor(message: string, code: string, options?: {
28
+ target?: string;
29
+ fix?: string;
30
+ details?: InitiativeResolutionDetails;
31
+ });
32
+ }
33
+ export interface InitiativeViewReference {
34
+ store: string;
35
+ storeSource: ContextStoreSelectorSource;
36
+ storeRoot: string;
37
+ id: string;
38
+ title: string;
39
+ summary: string;
40
+ created: string;
41
+ root: string;
42
+ storePath: string;
43
+ metadataPath: string;
44
+ }
45
+ export interface ListedInitiativeReference extends InitiativeViewReference {
46
+ status: InitiativeState['status'];
47
+ owners: InitiativeState['owners'];
48
+ metadata: InitiativeState['metadata'];
49
+ }
50
+ export type InitiativeDiagnosticSeverity = 'error' | 'warning';
51
+ export interface InitiativeDiagnostic {
52
+ severity: InitiativeDiagnosticSeverity;
53
+ code: string;
54
+ message: string;
55
+ target?: string;
56
+ fix?: string;
57
+ details?: InitiativeResolutionDetails;
58
+ }
59
+ export interface ContextStoreInitiativeListReference {
60
+ contextStore: SelectedContextStore;
61
+ initiatives: ListedInitiativeReference[];
62
+ status: InitiativeDiagnostic[];
63
+ }
64
+ export interface InitiativeListReferenceResult {
65
+ contextStore: SelectedContextStore | null;
66
+ contextStores: ContextStoreInitiativeListReference[];
67
+ initiatives: ListedInitiativeReference[];
68
+ status: InitiativeDiagnostic[];
69
+ }
70
+ export declare function initiativeDiagnosticFromError(error: unknown): InitiativeDiagnostic;
71
+ export declare function parseInitiativeReference(reference: string | undefined, options: InitiativeSelectorOptions): {
72
+ initiativeId: string;
73
+ options: InitiativeSelectorOptions;
74
+ };
75
+ export declare function resolveRegisteredInitiativeContextStore(storeId: string): Promise<SelectedContextStore>;
76
+ export declare function resolvePathInitiativeContextStore(storePath: string): Promise<SelectedContextStore>;
77
+ export declare function selectContextStoreForInitiative(options: InitiativeSelectorOptions, commandName: 'create' | 'list' | 'show'): Promise<SelectedContextStore>;
78
+ export declare function resolveSelectedInitiativeViewReference(selected: SelectedContextStore, initiativeId: string): Promise<InitiativeViewReference>;
79
+ export declare function listSelectedInitiativeViewReferences(selected: SelectedContextStore): Promise<ContextStoreInitiativeListReference>;
80
+ export declare function listInitiativeViewReferences(options?: InitiativeSelectorOptions): Promise<InitiativeListReferenceResult>;
81
+ export declare function resolveInitiativeViewReference(reference: string | undefined, options?: InitiativeSelectorOptions): Promise<InitiativeViewReference>;
82
+ export interface InitiativeLinkReference {
83
+ store: string;
84
+ id: string;
85
+ }
86
+ export declare function resolveInitiativeLinkReference(reference: string | undefined, options?: InitiativeSelectorOptions): Promise<InitiativeLinkReference>;
87
+ //# sourceMappingURL=resolution.d.ts.map