@kamilmarzynski/scifi 0.1.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 (257) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +232 -0
  3. package/dist/skills/sf-bug/manifest.d.ts +2 -0
  4. package/dist/skills/sf-bug/manifest.js +6 -0
  5. package/dist/skills/sf-bug/manifest.js.map +1 -0
  6. package/dist/skills/sf-change/manifest.d.ts +2 -0
  7. package/dist/skills/sf-change/manifest.js +6 -0
  8. package/dist/skills/sf-change/manifest.js.map +1 -0
  9. package/dist/skills/sf-code-review/manifest.d.ts +2 -0
  10. package/dist/skills/sf-code-review/manifest.js +5 -0
  11. package/dist/skills/sf-code-review/manifest.js.map +1 -0
  12. package/dist/skills/sf-continue/manifest.d.ts +2 -0
  13. package/dist/skills/sf-continue/manifest.js +6 -0
  14. package/dist/skills/sf-continue/manifest.js.map +1 -0
  15. package/dist/skills/sf-feature/manifest.d.ts +2 -0
  16. package/dist/skills/sf-feature/manifest.js +6 -0
  17. package/dist/skills/sf-feature/manifest.js.map +1 -0
  18. package/dist/skills/sf-fix/manifest.d.ts +2 -0
  19. package/dist/skills/sf-fix/manifest.js +6 -0
  20. package/dist/skills/sf-fix/manifest.js.map +1 -0
  21. package/dist/skills/sf-handover/manifest.d.ts +2 -0
  22. package/dist/skills/sf-handover/manifest.js +5 -0
  23. package/dist/skills/sf-handover/manifest.js.map +1 -0
  24. package/dist/skills/sf-implement/manifest.d.ts +2 -0
  25. package/dist/skills/sf-implement/manifest.js +6 -0
  26. package/dist/skills/sf-implement/manifest.js.map +1 -0
  27. package/dist/skills/sf-plan/manifest.d.ts +2 -0
  28. package/dist/skills/sf-plan/manifest.js +6 -0
  29. package/dist/skills/sf-plan/manifest.js.map +1 -0
  30. package/dist/skills/sf-plan-review/manifest.d.ts +2 -0
  31. package/dist/skills/sf-plan-review/manifest.js +5 -0
  32. package/dist/skills/sf-plan-review/manifest.js.map +1 -0
  33. package/dist/skills/sf-receiving-review/manifest.d.ts +2 -0
  34. package/dist/skills/sf-receiving-review/manifest.js +5 -0
  35. package/dist/skills/sf-receiving-review/manifest.js.map +1 -0
  36. package/dist/skills/sf-spec-review/manifest.d.ts +2 -0
  37. package/dist/skills/sf-spec-review/manifest.js +5 -0
  38. package/dist/skills/sf-spec-review/manifest.js.map +1 -0
  39. package/dist/skills/sf-tdd/manifest.d.ts +2 -0
  40. package/dist/skills/sf-tdd/manifest.js +5 -0
  41. package/dist/skills/sf-tdd/manifest.js.map +1 -0
  42. package/dist/skills/sf-verification/manifest.d.ts +2 -0
  43. package/dist/skills/sf-verification/manifest.js +5 -0
  44. package/dist/skills/sf-verification/manifest.js.map +1 -0
  45. package/dist/src/cli/commands/bug.d.ts +2 -0
  46. package/dist/src/cli/commands/bug.js +58 -0
  47. package/dist/src/cli/commands/bug.js.map +1 -0
  48. package/dist/src/cli/commands/finish.d.ts +2 -0
  49. package/dist/src/cli/commands/finish.js +43 -0
  50. package/dist/src/cli/commands/finish.js.map +1 -0
  51. package/dist/src/cli/commands/fix.d.ts +2 -0
  52. package/dist/src/cli/commands/fix.js +60 -0
  53. package/dist/src/cli/commands/fix.js.map +1 -0
  54. package/dist/src/cli/commands/init.d.ts +2 -0
  55. package/dist/src/cli/commands/init.js +92 -0
  56. package/dist/src/cli/commands/init.js.map +1 -0
  57. package/dist/src/cli/commands/list.d.ts +2 -0
  58. package/dist/src/cli/commands/list.js +52 -0
  59. package/dist/src/cli/commands/list.js.map +1 -0
  60. package/dist/src/cli/commands/plan-ready.d.ts +2 -0
  61. package/dist/src/cli/commands/plan-ready.js +27 -0
  62. package/dist/src/cli/commands/plan-ready.js.map +1 -0
  63. package/dist/src/cli/commands/plan.d.ts +2 -0
  64. package/dist/src/cli/commands/plan.js +43 -0
  65. package/dist/src/cli/commands/plan.js.map +1 -0
  66. package/dist/src/cli/commands/spec-ready.d.ts +2 -0
  67. package/dist/src/cli/commands/spec-ready.js +27 -0
  68. package/dist/src/cli/commands/spec-ready.js.map +1 -0
  69. package/dist/src/cli/commands/spec.d.ts +2 -0
  70. package/dist/src/cli/commands/spec.js +46 -0
  71. package/dist/src/cli/commands/spec.js.map +1 -0
  72. package/dist/src/cli/commands/start.d.ts +2 -0
  73. package/dist/src/cli/commands/start.js +27 -0
  74. package/dist/src/cli/commands/start.js.map +1 -0
  75. package/dist/src/cli/commands/status.d.ts +2 -0
  76. package/dist/src/cli/commands/status.js +62 -0
  77. package/dist/src/cli/commands/status.js.map +1 -0
  78. package/dist/src/cli/commands/task.d.ts +2 -0
  79. package/dist/src/cli/commands/task.js +64 -0
  80. package/dist/src/cli/commands/task.js.map +1 -0
  81. package/dist/src/cli/commands/worktree.d.ts +2 -0
  82. package/dist/src/cli/commands/worktree.js +33 -0
  83. package/dist/src/cli/commands/worktree.js.map +1 -0
  84. package/dist/src/cli/index.d.ts +3 -0
  85. package/dist/src/cli/index.js +106 -0
  86. package/dist/src/cli/index.js.map +1 -0
  87. package/dist/src/core/bugs/create.d.ts +13 -0
  88. package/dist/src/core/bugs/create.js +28 -0
  89. package/dist/src/core/bugs/create.js.map +1 -0
  90. package/dist/src/core/bugs/frontmatter.d.ts +7 -0
  91. package/dist/src/core/bugs/frontmatter.js +65 -0
  92. package/dist/src/core/bugs/frontmatter.js.map +1 -0
  93. package/dist/src/core/bugs/id.d.ts +1 -0
  94. package/dist/src/core/bugs/id.js +4 -0
  95. package/dist/src/core/bugs/id.js.map +1 -0
  96. package/dist/src/core/bugs/paths.d.ts +2 -0
  97. package/dist/src/core/bugs/paths.js +8 -0
  98. package/dist/src/core/bugs/paths.js.map +1 -0
  99. package/dist/src/core/bugs/types.d.ts +12 -0
  100. package/dist/src/core/bugs/types.js +3 -0
  101. package/dist/src/core/bugs/types.js.map +1 -0
  102. package/dist/src/core/fixes/create.d.ts +11 -0
  103. package/dist/src/core/fixes/create.js +43 -0
  104. package/dist/src/core/fixes/create.js.map +1 -0
  105. package/dist/src/core/fixes/frontmatter.d.ts +7 -0
  106. package/dist/src/core/fixes/frontmatter.js +50 -0
  107. package/dist/src/core/fixes/frontmatter.js.map +1 -0
  108. package/dist/src/core/fixes/id.d.ts +1 -0
  109. package/dist/src/core/fixes/id.js +4 -0
  110. package/dist/src/core/fixes/id.js.map +1 -0
  111. package/dist/src/core/fixes/list.d.ts +8 -0
  112. package/dist/src/core/fixes/list.js +43 -0
  113. package/dist/src/core/fixes/list.js.map +1 -0
  114. package/dist/src/core/fixes/paths.d.ts +2 -0
  115. package/dist/src/core/fixes/paths.js +9 -0
  116. package/dist/src/core/fixes/paths.js.map +1 -0
  117. package/dist/src/core/fixes/transition.d.ts +9 -0
  118. package/dist/src/core/fixes/transition.js +26 -0
  119. package/dist/src/core/fixes/transition.js.map +1 -0
  120. package/dist/src/core/fixes/types.d.ts +9 -0
  121. package/dist/src/core/fixes/types.js +2 -0
  122. package/dist/src/core/fixes/types.js.map +1 -0
  123. package/dist/src/core/init/config.d.ts +6 -0
  124. package/dist/src/core/init/config.js +18 -0
  125. package/dist/src/core/init/config.js.map +1 -0
  126. package/dist/src/core/init/install-skills.d.ts +8 -0
  127. package/dist/src/core/init/install-skills.js +14 -0
  128. package/dist/src/core/init/install-skills.js.map +1 -0
  129. package/dist/src/core/init/prompt-harness.d.ts +8 -0
  130. package/dist/src/core/init/prompt-harness.js +19 -0
  131. package/dist/src/core/init/prompt-harness.js.map +1 -0
  132. package/dist/src/core/init/scaffold.d.ts +5 -0
  133. package/dist/src/core/init/scaffold.js +91 -0
  134. package/dist/src/core/init/scaffold.js.map +1 -0
  135. package/dist/src/core/init/types.d.ts +5 -0
  136. package/dist/src/core/init/types.js +2 -0
  137. package/dist/src/core/init/types.js.map +1 -0
  138. package/dist/src/core/output/emit.d.ts +8 -0
  139. package/dist/src/core/output/emit.js +50 -0
  140. package/dist/src/core/output/emit.js.map +1 -0
  141. package/dist/src/core/output/errors.d.ts +14 -0
  142. package/dist/src/core/output/errors.js +36 -0
  143. package/dist/src/core/output/errors.js.map +1 -0
  144. package/dist/src/core/output/index.d.ts +4 -0
  145. package/dist/src/core/output/index.js +4 -0
  146. package/dist/src/core/output/index.js.map +1 -0
  147. package/dist/src/core/output/tty.d.ts +1 -0
  148. package/dist/src/core/output/tty.js +4 -0
  149. package/dist/src/core/output/tty.js.map +1 -0
  150. package/dist/src/core/package-root.d.ts +1 -0
  151. package/dist/src/core/package-root.js +18 -0
  152. package/dist/src/core/package-root.js.map +1 -0
  153. package/dist/src/core/skills/catalog.d.ts +6 -0
  154. package/dist/src/core/skills/catalog.js +69 -0
  155. package/dist/src/core/skills/catalog.js.map +1 -0
  156. package/dist/src/core/skills/harness/adapter.d.ts +15 -0
  157. package/dist/src/core/skills/harness/adapter.js +31 -0
  158. package/dist/src/core/skills/harness/adapter.js.map +1 -0
  159. package/dist/src/core/skills/harness/claude-code.d.ts +2 -0
  160. package/dist/src/core/skills/harness/claude-code.js +37 -0
  161. package/dist/src/core/skills/harness/claude-code.js.map +1 -0
  162. package/dist/src/core/skills/harness/register-defaults.d.ts +1 -0
  163. package/dist/src/core/skills/harness/register-defaults.js +4 -0
  164. package/dist/src/core/skills/harness/register-defaults.js.map +1 -0
  165. package/dist/src/core/skills/harness/registry.d.ts +3 -0
  166. package/dist/src/core/skills/harness/registry.js +22 -0
  167. package/dist/src/core/skills/harness/registry.js.map +1 -0
  168. package/dist/src/core/skills/types.d.ts +18 -0
  169. package/dist/src/core/skills/types.js +9 -0
  170. package/dist/src/core/skills/types.js.map +1 -0
  171. package/dist/src/core/slugify.d.ts +2 -0
  172. package/dist/src/core/slugify.js +13 -0
  173. package/dist/src/core/slugify.js.map +1 -0
  174. package/dist/src/core/specs/create.d.ts +11 -0
  175. package/dist/src/core/specs/create.js +35 -0
  176. package/dist/src/core/specs/create.js.map +1 -0
  177. package/dist/src/core/specs/id.d.ts +1 -0
  178. package/dist/src/core/specs/id.js +4 -0
  179. package/dist/src/core/specs/id.js.map +1 -0
  180. package/dist/src/core/specs/lifecycle.d.ts +17 -0
  181. package/dist/src/core/specs/lifecycle.js +97 -0
  182. package/dist/src/core/specs/lifecycle.js.map +1 -0
  183. package/dist/src/core/specs/list.d.ts +6 -0
  184. package/dist/src/core/specs/list.js +43 -0
  185. package/dist/src/core/specs/list.js.map +1 -0
  186. package/dist/src/core/specs/metadata.d.ts +3 -0
  187. package/dist/src/core/specs/metadata.js +13 -0
  188. package/dist/src/core/specs/metadata.js.map +1 -0
  189. package/dist/src/core/specs/paths.d.ts +3 -0
  190. package/dist/src/core/specs/paths.js +13 -0
  191. package/dist/src/core/specs/paths.js.map +1 -0
  192. package/dist/src/core/specs/plan-session.d.ts +18 -0
  193. package/dist/src/core/specs/plan-session.js +24 -0
  194. package/dist/src/core/specs/plan-session.js.map +1 -0
  195. package/dist/src/core/specs/transition.d.ts +8 -0
  196. package/dist/src/core/specs/transition.js +33 -0
  197. package/dist/src/core/specs/transition.js.map +1 -0
  198. package/dist/src/core/specs/types.d.ts +17 -0
  199. package/dist/src/core/specs/types.js +8 -0
  200. package/dist/src/core/specs/types.js.map +1 -0
  201. package/dist/src/core/specs/worktree.d.ts +10 -0
  202. package/dist/src/core/specs/worktree.js +32 -0
  203. package/dist/src/core/specs/worktree.js.map +1 -0
  204. package/dist/src/core/tasks/frontmatter.d.ts +7 -0
  205. package/dist/src/core/tasks/frontmatter.js +53 -0
  206. package/dist/src/core/tasks/frontmatter.js.map +1 -0
  207. package/dist/src/core/tasks/list.d.ts +2 -0
  208. package/dist/src/core/tasks/list.js +18 -0
  209. package/dist/src/core/tasks/list.js.map +1 -0
  210. package/dist/src/core/tasks/paths.d.ts +2 -0
  211. package/dist/src/core/tasks/paths.js +11 -0
  212. package/dist/src/core/tasks/paths.js.map +1 -0
  213. package/dist/src/core/tasks/transition.d.ts +8 -0
  214. package/dist/src/core/tasks/transition.js +30 -0
  215. package/dist/src/core/tasks/transition.js.map +1 -0
  216. package/dist/src/core/tasks/types.d.ts +8 -0
  217. package/dist/src/core/tasks/types.js +2 -0
  218. package/dist/src/core/tasks/types.js.map +1 -0
  219. package/package.json +67 -0
  220. package/skills/sf-bug/DISPATCH-CODE-REVIEW.md +22 -0
  221. package/skills/sf-bug/body.md +117 -0
  222. package/skills/sf-bug/manifest.ts +8 -0
  223. package/skills/sf-change/body.md +178 -0
  224. package/skills/sf-change/manifest.ts +8 -0
  225. package/skills/sf-code-review/body.md +155 -0
  226. package/skills/sf-code-review/manifest.ts +7 -0
  227. package/skills/sf-continue/body.md +90 -0
  228. package/skills/sf-continue/manifest.ts +8 -0
  229. package/skills/sf-feature/ADR-TEMPLATE.md +16 -0
  230. package/skills/sf-feature/DISPATCH-SPEC-REVIEW.md +16 -0
  231. package/skills/sf-feature/SPEC-TEMPLATE.md +37 -0
  232. package/skills/sf-feature/body.md +145 -0
  233. package/skills/sf-feature/manifest.ts +8 -0
  234. package/skills/sf-fix/DISPATCH-CODE-REVIEW.md +25 -0
  235. package/skills/sf-fix/body.md +174 -0
  236. package/skills/sf-fix/manifest.ts +8 -0
  237. package/skills/sf-handover/body.md +67 -0
  238. package/skills/sf-handover/manifest.ts +7 -0
  239. package/skills/sf-implement/DISPATCH-CODE-REVIEW.md +19 -0
  240. package/skills/sf-implement/DISPATCH-HANDOVER.md +19 -0
  241. package/skills/sf-implement/DISPATCH-IMPLEMENTER.md +36 -0
  242. package/skills/sf-implement/body.md +147 -0
  243. package/skills/sf-implement/manifest.ts +8 -0
  244. package/skills/sf-plan/ADR-TEMPLATE.md +16 -0
  245. package/skills/sf-plan/DESIGN-TEMPLATE.md +54 -0
  246. package/skills/sf-plan/DISPATCH-PLAN-REVIEW.md +17 -0
  247. package/skills/sf-plan/TASK-TEMPLATE.md +30 -0
  248. package/skills/sf-plan/body.md +162 -0
  249. package/skills/sf-plan/manifest.ts +8 -0
  250. package/skills/sf-plan-review/body.md +90 -0
  251. package/skills/sf-plan-review/manifest.ts +7 -0
  252. package/skills/sf-receiving-review/body.md +73 -0
  253. package/skills/sf-receiving-review/manifest.ts +7 -0
  254. package/skills/sf-spec-review/body.md +83 -0
  255. package/skills/sf-spec-review/manifest.ts +7 -0
  256. package/skills/sf-tdd/body.md +120 -0
  257. package/skills/sf-tdd/manifest.ts +7 -0
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ declare const skillManifestSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ description: z.ZodString;
5
+ allowedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ argumentHint: z.ZodOptional<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ export { skillManifestSchema };
9
+ export type SkillManifest = z.infer<typeof skillManifestSchema>;
10
+ export interface SkillAsset {
11
+ readonly name: string;
12
+ readonly contents: string;
13
+ }
14
+ export interface SkillBundle {
15
+ readonly manifest: SkillManifest;
16
+ readonly body: string;
17
+ readonly assets: readonly SkillAsset[];
18
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ const skillManifestSchema = z.object({
3
+ id: z.string().min(1),
4
+ description: z.string().min(1),
5
+ allowedTools: z.array(z.string()).optional(),
6
+ argumentHint: z.string().optional(),
7
+ });
8
+ export { skillManifestSchema };
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/core/skills/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC5C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAC;AAEH,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function assertSafeSlug(value: string, label: string): void;
2
+ export declare function slugify(text: string): string;
@@ -0,0 +1,13 @@
1
+ const SAFE_SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
2
+ export function assertSafeSlug(value, label) {
3
+ if (!SAFE_SLUG_RE.test(value)) {
4
+ throw new Error(`Invalid ${label} "${value}". Use lowercase letters, numbers, and single hyphens.`);
5
+ }
6
+ }
7
+ export function slugify(text) {
8
+ return text
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9]+/g, '-')
11
+ .replace(/^-+|-+$/g, '');
12
+ }
13
+ //# sourceMappingURL=slugify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.js","sourceRoot":"","sources":["../../../src/core/slugify.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAAG,0BAA0B,CAAC;AAEhD,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,KAAa;IACzD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,WAAW,KAAK,KAAK,KAAK,wDAAwD,CACnF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,11 @@
1
+ export interface CreateFeatureOptions {
2
+ projectRoot: string;
3
+ slug: string;
4
+ title?: string;
5
+ now: string;
6
+ }
7
+ export interface CreateFeatureResult {
8
+ featureDirectoryPath: string;
9
+ metadataPath: string;
10
+ }
11
+ export declare function createFeature(options: CreateFeatureOptions): Promise<CreateFeatureResult>;
@@ -0,0 +1,35 @@
1
+ import { mkdir, stat, writeFile } from 'node:fs/promises';
2
+ import { ScifiError } from '../output/errors.js';
3
+ import { createInitialFeatureMetadata } from './metadata.js';
4
+ import { buildFeatureDirectoryPath, buildFeatureMetadataPath, buildFeaturesRootPath, } from './paths.js';
5
+ export async function createFeature(options) {
6
+ const { projectRoot, slug, title, now } = options;
7
+ const featuresRootPath = buildFeaturesRootPath(projectRoot);
8
+ const featureDirectoryPath = buildFeatureDirectoryPath(projectRoot, slug);
9
+ const metadataPath = buildFeatureMetadataPath(projectRoot, slug);
10
+ const existingFeatureDirectory = await stat(featureDirectoryPath).catch((error) => {
11
+ if (isMissingPathError(error)) {
12
+ return null;
13
+ }
14
+ throw error;
15
+ });
16
+ if (existingFeatureDirectory !== null) {
17
+ throw new ScifiError('CONFLICT', `Cannot create feature ${slug}: ${featureDirectoryPath} already exists.`, { hint: 'Choose a different slug or inspect it with `scifi status <slug>`.' });
18
+ }
19
+ await mkdir(featuresRootPath, { recursive: true });
20
+ await mkdir(featureDirectoryPath, { recursive: false });
21
+ const metadata = createInitialFeatureMetadata({
22
+ slug,
23
+ ...(title !== undefined && { title }),
24
+ createdAt: now,
25
+ });
26
+ await writeFile(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8');
27
+ return {
28
+ featureDirectoryPath,
29
+ metadataPath,
30
+ };
31
+ }
32
+ function isMissingPathError(error) {
33
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
34
+ }
35
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../../../src/core/specs/create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAcpB,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA6B;IAC/D,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IAClD,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,oBAAoB,GAAG,yBAAyB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAC1E,MAAM,YAAY,GAAG,wBAAwB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAEjE,MAAM,wBAAwB,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,CAAC,KAAK,CACrE,CAAC,KAAc,EAAQ,EAAE;QACvB,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,CAAC;IACd,CAAC,CACF,CAAC;IAEF,IAAI,wBAAwB,KAAK,IAAI,EAAE,CAAC;QACtC,MAAM,IAAI,UAAU,CAClB,UAAU,EACV,yBAAyB,IAAI,KAAK,oBAAoB,kBAAkB,EACxE,EAAE,IAAI,EAAE,mEAAmE,EAAE,CAC9E,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEnD,MAAM,KAAK,CAAC,oBAAoB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IAExD,MAAM,QAAQ,GAAG,4BAA4B,CAAC;QAC5C,IAAI;QACJ,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC;QACrC,SAAS,EAAE,GAAG;KACf,CAAC,CAAC;IAEH,MAAM,SAAS,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEhF,OAAO;QACL,oBAAoB;QACpB,YAAY;KACb,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACxC,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC9E,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function formatFeatureId(sequenceNumber: number): string;
@@ -0,0 +1,4 @@
1
+ export function formatFeatureId(sequenceNumber) {
2
+ return `FEAT-${sequenceNumber.toString().padStart(4, '0')}`;
3
+ }
4
+ //# sourceMappingURL=id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.js","sourceRoot":"","sources":["../../../../src/core/specs/id.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe,CAAC,cAAsB;IACpD,OAAO,QAAQ,cAAc,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { FeatureMetadata, FeatureStatus } from './types.js';
2
+ export interface FeatureArtifacts {
3
+ specExists: boolean;
4
+ designExists: boolean;
5
+ taskFileCount: number;
6
+ }
7
+ export interface FeatureLifecycle {
8
+ metadata: FeatureMetadata;
9
+ artifacts: FeatureArtifacts;
10
+ }
11
+ export declare function inspectFeatureLifecycle(projectRoot: string, slug: string): Promise<FeatureLifecycle>;
12
+ interface ValidationContext {
13
+ currentStatus?: FeatureStatus;
14
+ allTasksDone?: boolean;
15
+ }
16
+ export declare function validateStatusTransition(artifacts: FeatureArtifacts, targetStatus: FeatureStatus, context?: ValidationContext): Promise<void>;
17
+ export {};
@@ -0,0 +1,97 @@
1
+ import { readdir, readFile, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { ScifiError } from '../output/errors.js';
4
+ import { buildFeatureDirectoryPath, buildFeatureMetadataPath } from './paths.js';
5
+ function isMissingPathError(error) {
6
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
7
+ }
8
+ async function pathIsRegularFile(filePath) {
9
+ try {
10
+ const entry = await stat(filePath);
11
+ return entry.isFile();
12
+ }
13
+ catch (error) {
14
+ if (isMissingPathError(error)) {
15
+ return false;
16
+ }
17
+ throw error;
18
+ }
19
+ }
20
+ function isValidFeatureMetadata(value) {
21
+ if (typeof value !== 'object' || value === null) {
22
+ return false;
23
+ }
24
+ const obj = value;
25
+ return ('version' in obj &&
26
+ typeof obj.version === 'number' &&
27
+ 'slug' in obj &&
28
+ typeof obj.slug === 'string' &&
29
+ 'status' in obj &&
30
+ typeof obj.status === 'string' &&
31
+ 'createdAt' in obj &&
32
+ typeof obj.createdAt === 'string' &&
33
+ 'updatedAt' in obj &&
34
+ typeof obj.updatedAt === 'string');
35
+ }
36
+ export async function inspectFeatureLifecycle(projectRoot, slug) {
37
+ const featureRoot = buildFeatureDirectoryPath(projectRoot, slug);
38
+ const metadataPath = buildFeatureMetadataPath(projectRoot, slug);
39
+ const rawMetadata = await readFile(metadataPath, 'utf8').catch((error) => {
40
+ if (isMissingPathError(error)) {
41
+ throw new ScifiError('NOT_FOUND', `Feature "${slug}" does not exist.`, {
42
+ hint: `Create it with \`scifi spec ${slug}\`.`,
43
+ cause: error,
44
+ });
45
+ }
46
+ throw error;
47
+ });
48
+ const parsed = JSON.parse(rawMetadata);
49
+ if (!isValidFeatureMetadata(parsed)) {
50
+ throw new ScifiError('INTERNAL', `Invalid metadata file at ${metadataPath}`);
51
+ }
52
+ const metadata = parsed;
53
+ const specExists = await pathIsRegularFile(join(featureRoot, 'spec.md'));
54
+ const designExists = await pathIsRegularFile(join(featureRoot, 'design.md'));
55
+ const taskEntries = await readdir(join(featureRoot, 'tasks'), {
56
+ withFileTypes: true,
57
+ }).catch((error) => {
58
+ if (isMissingPathError(error)) {
59
+ return [];
60
+ }
61
+ throw error;
62
+ });
63
+ const taskFileCount = taskEntries.filter((entry) => entry.isFile() && entry.name.endsWith('.md')).length;
64
+ return {
65
+ metadata,
66
+ artifacts: {
67
+ specExists,
68
+ designExists,
69
+ taskFileCount,
70
+ },
71
+ };
72
+ }
73
+ export async function validateStatusTransition(artifacts, targetStatus, context) {
74
+ if (targetStatus === 'spec-ready' && !artifacts.specExists) {
75
+ throw new ScifiError('PRECONDITION_FAILED', 'Cannot mark feature as spec-ready: spec.md is missing.', { hint: 'Write spec.md in the feature directory, then retry.' });
76
+ }
77
+ if (targetStatus === 'plan-ready') {
78
+ if (!artifacts.designExists) {
79
+ throw new ScifiError('PRECONDITION_FAILED', 'Cannot mark feature as plan-ready: design.md is missing.', { hint: 'Write design.md in the feature directory, then retry.' });
80
+ }
81
+ if (artifacts.taskFileCount < 1) {
82
+ throw new ScifiError('PRECONDITION_FAILED', 'Cannot mark feature as plan-ready: no task files were found.', { hint: "Add at least one task under the feature's tasks/ directory." });
83
+ }
84
+ }
85
+ if (targetStatus === 'in-progress' &&
86
+ context?.currentStatus !== undefined &&
87
+ context.currentStatus !== 'plan-ready' &&
88
+ // Starting an already in-progress feature is an idempotent no-op so a
89
+ // resumed run (e.g. via sf-continue) passes through instead of failing.
90
+ context.currentStatus !== 'in-progress') {
91
+ throw new ScifiError('PRECONDITION_FAILED', 'Cannot start feature: feature must be plan-ready before starting implementation.', { hint: 'Run `scifi plan-ready <slug>` first.' });
92
+ }
93
+ if (targetStatus === 'done' && context?.allTasksDone === false) {
94
+ throw new ScifiError('PRECONDITION_FAILED', 'Cannot mark feature as done: not all tasks are complete.', { hint: 'Finish remaining tasks with `scifi task done <slug> <task>`.' });
95
+ }
96
+ }
97
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../../../src/core/specs/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { FeatureMetadata, FeatureStatus } from './types.js';
2
+ export interface ListFeaturesOptions {
3
+ projectRoot: string;
4
+ status?: FeatureStatus;
5
+ }
6
+ export declare function listFeatures(options: ListFeaturesOptions): Promise<FeatureMetadata[]>;
@@ -0,0 +1,43 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { buildFeatureMetadataPath, buildFeaturesRootPath } from './paths.js';
3
+ function isMissingPathError(error) {
4
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
5
+ }
6
+ function isValidFeatureMetadata(value) {
7
+ if (typeof value !== 'object' || value === null)
8
+ return false;
9
+ const obj = value;
10
+ return ('version' in obj &&
11
+ typeof obj.version === 'number' &&
12
+ 'slug' in obj &&
13
+ typeof obj.slug === 'string' &&
14
+ 'status' in obj &&
15
+ typeof obj.status === 'string' &&
16
+ 'createdAt' in obj &&
17
+ typeof obj.createdAt === 'string' &&
18
+ 'updatedAt' in obj &&
19
+ typeof obj.updatedAt === 'string');
20
+ }
21
+ export async function listFeatures(options) {
22
+ const { projectRoot, status } = options;
23
+ const specsRoot = buildFeaturesRootPath(projectRoot);
24
+ const entries = await readdir(specsRoot, { withFileTypes: true }).catch((error) => {
25
+ if (isMissingPathError(error))
26
+ return [];
27
+ throw error;
28
+ });
29
+ const featureDirs = entries.filter((entry) => entry.isDirectory());
30
+ const allResults = await Promise.all(featureDirs.map(async (dir) => {
31
+ const metadataPath = buildFeatureMetadataPath(projectRoot, dir.name);
32
+ const raw = JSON.parse(await readFile(metadataPath, 'utf8'));
33
+ if (!isValidFeatureMetadata(raw))
34
+ return null;
35
+ return raw;
36
+ }));
37
+ const features = allResults.filter((m) => m !== null);
38
+ if (status !== undefined) {
39
+ return features.filter((f) => f.status === status);
40
+ }
41
+ return features;
42
+ }
43
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../../src/core/specs/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAG7E,SAAS,kBAAkB,CAAC,KAAc;IACxC,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC9E,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAc;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,OAAO,CACL,SAAS,IAAI,GAAG;QAChB,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;QAC/B,MAAM,IAAI,GAAG;QACb,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ;QAC5B,QAAQ,IAAI,GAAG;QACf,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QAC9B,WAAW,IAAI,GAAG;QAClB,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACjC,WAAW,IAAI,GAAG;QAClB,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAClC,CAAC;AACJ,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA4B;IAC7D,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACxC,MAAM,SAAS,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAErD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACzF,IAAI,kBAAkB,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACzC,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IAEnE,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,GAAG,CAClC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5B,MAAM,YAAY,GAAG,wBAAwB,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAY,CAAC;QACxE,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAwB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAE5E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CreateFeatureMetadataInput, FeatureMetadata } from './types.js';
2
+ export { buildFeatureDirectoryPath, buildFeatureMetadataPath, } from './paths.js';
3
+ export declare function createInitialFeatureMetadata(input: CreateFeatureMetadataInput): FeatureMetadata;
@@ -0,0 +1,13 @@
1
+ export { buildFeatureDirectoryPath, buildFeatureMetadataPath, } from './paths.js';
2
+ export function createInitialFeatureMetadata(input) {
3
+ const { slug, title, createdAt } = input;
4
+ return {
5
+ version: 1,
6
+ slug,
7
+ ...(title !== undefined && { title }),
8
+ status: 'created',
9
+ createdAt,
10
+ updatedAt: createdAt,
11
+ };
12
+ }
13
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../../../src/core/specs/metadata.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,YAAY,CAAC;AAEpB,MAAM,UAAU,4BAA4B,CAAC,KAAiC;IAC5E,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAEzC,OAAO;QACL,OAAO,EAAE,CAAC;QACV,IAAI;QACJ,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC;QACrC,MAAM,EAAE,SAAS;QACjB,SAAS;QACT,SAAS,EAAE,SAAS;KACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function buildFeaturesRootPath(projectRoot: string): string;
2
+ export declare function buildFeatureDirectoryPath(projectRoot: string, slug: string): string;
3
+ export declare function buildFeatureMetadataPath(projectRoot: string, slug: string): string;
@@ -0,0 +1,13 @@
1
+ import { join } from 'node:path';
2
+ import { assertSafeSlug } from '../slugify.js';
3
+ export function buildFeaturesRootPath(projectRoot) {
4
+ return join(projectRoot, 'docs', 'scifi', 'specs');
5
+ }
6
+ export function buildFeatureDirectoryPath(projectRoot, slug) {
7
+ assertSafeSlug(slug, 'feature slug');
8
+ return join(buildFeaturesRootPath(projectRoot), slug);
9
+ }
10
+ export function buildFeatureMetadataPath(projectRoot, slug) {
11
+ return join(buildFeatureDirectoryPath(projectRoot, slug), '.scifi.json');
12
+ }
13
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/core/specs/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,UAAU,qBAAqB,CAAC,WAAmB;IACvD,OAAO,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,WAAmB,EAAE,IAAY;IACzE,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC,qBAAqB,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,WAAmB,EAAE,IAAY;IACxE,OAAO,IAAI,CAAC,yBAAyB,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,aAAa,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { FeatureStatus } from './types.js';
2
+ /**
3
+ * Where a feature sits relative to its planning session.
4
+ *
5
+ * - `ready-to-plan` — spec-ready, no design/tasks written yet (start fresh).
6
+ * - `in-progress` — spec-ready with partial design/tasks (resume).
7
+ * - `already-planned`— plan-ready or beyond (offer continue vs restart).
8
+ */
9
+ export type PlanSessionState = 'ready-to-plan' | 'in-progress' | 'already-planned';
10
+ export interface PlanSession {
11
+ slug: string;
12
+ title?: string;
13
+ status: FeatureStatus;
14
+ state: PlanSessionState;
15
+ designExists: boolean;
16
+ taskFileCount: number;
17
+ }
18
+ export declare function inspectPlanSession(projectRoot: string, slug: string): Promise<PlanSession>;
@@ -0,0 +1,24 @@
1
+ import { ScifiError } from '../output/errors.js';
2
+ import { inspectFeatureLifecycle } from './lifecycle.js';
3
+ export async function inspectPlanSession(projectRoot, slug) {
4
+ const { metadata, artifacts } = await inspectFeatureLifecycle(projectRoot, slug);
5
+ if (metadata.status === 'created') {
6
+ throw new ScifiError('PRECONDITION_FAILED', `Cannot plan "${slug}": feature is not spec-ready yet.`, { hint: `Finish the spec and run \`scifi spec-ready ${slug}\` first.` });
7
+ }
8
+ let state;
9
+ if (metadata.status === 'spec-ready') {
10
+ state = artifacts.designExists || artifacts.taskFileCount > 0 ? 'in-progress' : 'ready-to-plan';
11
+ }
12
+ else {
13
+ state = 'already-planned';
14
+ }
15
+ return {
16
+ slug: metadata.slug,
17
+ ...(metadata.title !== undefined && { title: metadata.title }),
18
+ status: metadata.status,
19
+ state,
20
+ designExists: artifacts.designExists,
21
+ taskFileCount: artifacts.taskFileCount,
22
+ };
23
+ }
24
+ //# sourceMappingURL=plan-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-session.js","sourceRoot":"","sources":["../../../../src/core/specs/plan-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAqBzD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,WAAmB,EAAE,IAAY;IACxE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM,uBAAuB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAEjF,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,IAAI,UAAU,CAClB,qBAAqB,EACrB,gBAAgB,IAAI,mCAAmC,EACvD,EAAE,IAAI,EAAE,8CAA8C,IAAI,WAAW,EAAE,CACxE,CAAC;IACJ,CAAC;IAED,IAAI,KAAuB,CAAC;IAC5B,IAAI,QAAQ,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACrC,KAAK,GAAG,SAAS,CAAC,YAAY,IAAI,SAAS,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC;IAClG,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,iBAAiB,CAAC;IAC5B,CAAC;IAED,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;QAC9D,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,KAAK;QACL,YAAY,EAAE,SAAS,CAAC,YAAY;QACpC,aAAa,EAAE,SAAS,CAAC,aAAa;KACvC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { FeatureStatus } from './types.js';
2
+ export interface UpdateFeatureStatusResult {
3
+ slug: string;
4
+ previousStatus: FeatureStatus;
5
+ newStatus: FeatureStatus;
6
+ timestamp: string;
7
+ }
8
+ export declare function updateFeatureStatus(projectRoot: string, slug: string, targetStatus: FeatureStatus, now: string): Promise<UpdateFeatureStatusResult>;
@@ -0,0 +1,33 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { listTasks } from '../tasks/list.js';
3
+ import { inspectFeatureLifecycle, validateStatusTransition } from './lifecycle.js';
4
+ import { buildFeatureMetadataPath } from './paths.js';
5
+ export async function updateFeatureStatus(projectRoot, slug, targetStatus, now) {
6
+ const lifecycle = await inspectFeatureLifecycle(projectRoot, slug);
7
+ const tasks = await listTasks(projectRoot, slug);
8
+ const allTasksDone = tasks.length > 0 && tasks.every((t) => t.status === 'done');
9
+ await validateStatusTransition(lifecycle.artifacts, targetStatus, {
10
+ currentStatus: lifecycle.metadata.status,
11
+ allTasksDone,
12
+ });
13
+ const metadata = lifecycle.metadata;
14
+ const updatedMetadata = {
15
+ version: metadata.version,
16
+ slug: metadata.slug,
17
+ ...(metadata.title !== undefined && { title: metadata.title }),
18
+ status: targetStatus,
19
+ createdAt: metadata.createdAt,
20
+ updatedAt: now,
21
+ ...(metadata.branch !== undefined && { branch: metadata.branch }),
22
+ ...(metadata.worktreePath !== undefined && { worktreePath: metadata.worktreePath }),
23
+ };
24
+ const metadataPath = buildFeatureMetadataPath(projectRoot, slug);
25
+ await writeFile(metadataPath, `${JSON.stringify(updatedMetadata, null, 2)}\n`, 'utf8');
26
+ return {
27
+ slug: metadata.slug,
28
+ previousStatus: metadata.status,
29
+ newStatus: targetStatus,
30
+ timestamp: now,
31
+ };
32
+ }
33
+ //# sourceMappingURL=transition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transition.js","sourceRoot":"","sources":["../../../../src/core/specs/transition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAUtD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,WAAmB,EACnB,IAAY,EACZ,YAA2B,EAC3B,GAAW;IAEX,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACjD,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IAEjF,MAAM,wBAAwB,CAAC,SAAS,CAAC,SAAS,EAAE,YAAY,EAAE;QAChE,aAAa,EAAE,SAAS,CAAC,QAAQ,CAAC,MAAM;QACxC,YAAY;KACb,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC;IACpC,MAAM,eAAe,GAAoB;QACvC,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;QAC9D,MAAM,EAAE,YAAY;QACpB,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,SAAS,EAAE,GAAG;QACd,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjE,GAAG,CAAC,QAAQ,CAAC,YAAY,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,CAAC;KACpF,CAAC;IAEF,MAAM,YAAY,GAAG,wBAAwB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACjE,MAAM,SAAS,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEvF,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,cAAc,EAAE,QAAQ,CAAC,MAAM;QAC/B,SAAS,EAAE,YAAY;QACvB,SAAS,EAAE,GAAG;KACf,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ export declare const FEATURE_STATUS_VALUES: readonly ["created", "spec-ready", "plan-ready", "in-progress", "done"];
2
+ export type FeatureStatus = (typeof FEATURE_STATUS_VALUES)[number];
3
+ export interface FeatureMetadata {
4
+ version: 1;
5
+ slug: string;
6
+ title?: string;
7
+ status: FeatureStatus;
8
+ createdAt: string;
9
+ updatedAt: string;
10
+ branch?: string;
11
+ worktreePath?: string;
12
+ }
13
+ export interface CreateFeatureMetadataInput {
14
+ slug: string;
15
+ title?: string;
16
+ createdAt: string;
17
+ }
@@ -0,0 +1,8 @@
1
+ export const FEATURE_STATUS_VALUES = [
2
+ 'created',
3
+ 'spec-ready',
4
+ 'plan-ready',
5
+ 'in-progress',
6
+ 'done',
7
+ ];
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/core/specs/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,SAAS;IACT,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,MAAM;CACE,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface SetFeatureWorktreeInput {
2
+ branch: string;
3
+ path: string;
4
+ }
5
+ export interface SetFeatureWorktreeResult {
6
+ slug: string;
7
+ branch: string;
8
+ worktreePath: string;
9
+ }
10
+ export declare function setFeatureWorktree(projectRoot: string, slug: string, input: SetFeatureWorktreeInput): Promise<SetFeatureWorktreeResult>;
@@ -0,0 +1,32 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { ScifiError } from '../output/errors.js';
3
+ import { inspectFeatureLifecycle } from './lifecycle.js';
4
+ import { buildFeatureMetadataPath } from './paths.js';
5
+ export async function setFeatureWorktree(projectRoot, slug, input) {
6
+ const branch = input.branch.trim();
7
+ const worktreePath = input.path.trim();
8
+ if (branch.length === 0) {
9
+ throw new ScifiError('INVALID_ARGUMENT', 'Branch must not be empty.', {
10
+ hint: 'Pass --branch <branch-name>.',
11
+ });
12
+ }
13
+ if (worktreePath.length === 0) {
14
+ throw new ScifiError('INVALID_ARGUMENT', 'Worktree path must not be empty.', {
15
+ hint: 'Pass --path <worktree-path>.',
16
+ });
17
+ }
18
+ const { metadata } = await inspectFeatureLifecycle(projectRoot, slug);
19
+ const updatedMetadata = {
20
+ ...metadata,
21
+ branch,
22
+ worktreePath,
23
+ };
24
+ const metadataPath = buildFeatureMetadataPath(projectRoot, slug);
25
+ await writeFile(metadataPath, `${JSON.stringify(updatedMetadata, null, 2)}\n`, 'utf8');
26
+ return {
27
+ slug: metadata.slug,
28
+ branch,
29
+ worktreePath,
30
+ };
31
+ }
32
+ //# sourceMappingURL=worktree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../../../src/core/specs/worktree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AActD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,IAAY,EACZ,KAA8B;IAE9B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACnC,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAEvC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAAC,kBAAkB,EAAE,2BAA2B,EAAE;YACpE,IAAI,EAAE,8BAA8B;SACrC,CAAC,CAAC;IACL,CAAC;IACD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CAAC,kBAAkB,EAAE,kCAAkC,EAAE;YAC3E,IAAI,EAAE,8BAA8B;SACrC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,uBAAuB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAEtE,MAAM,eAAe,GAAoB;QACvC,GAAG,QAAQ;QACX,MAAM;QACN,YAAY;KACb,CAAC;IAEF,MAAM,YAAY,GAAG,wBAAwB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACjE,MAAM,SAAS,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEvF,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,MAAM;QACN,YAAY;KACb,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type TaskFrontmatter } from './types.js';
2
+ export interface TaskFile {
3
+ frontmatter: TaskFrontmatter;
4
+ body: string;
5
+ }
6
+ export declare function readTaskFile(filePath: string): Promise<TaskFile>;
7
+ export declare function writeTaskFile(filePath: string, file: TaskFile): Promise<void>;
@@ -0,0 +1,53 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
3
+ import { TASK_STATUS_VALUES } from './types.js';
4
+ function isValidTaskStatus(value) {
5
+ return typeof value === 'string' && TASK_STATUS_VALUES.includes(value);
6
+ }
7
+ function isValidRawFrontmatter(raw) {
8
+ if (typeof raw !== 'object' || raw === null)
9
+ return false;
10
+ const obj = raw;
11
+ const allowedKeys = new Set(['id', 'slug', 'status', 'depends-on']);
12
+ if (Object.keys(obj).some((key) => !allowedKeys.has(key)))
13
+ return false;
14
+ return (typeof obj.id === 'string' &&
15
+ typeof obj.slug === 'string' &&
16
+ isValidTaskStatus(obj.status) &&
17
+ Array.isArray(obj['depends-on']));
18
+ }
19
+ const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
20
+ export async function readTaskFile(filePath) {
21
+ const content = await readFile(filePath, 'utf8');
22
+ const match = FRONTMATTER_PATTERN.exec(content);
23
+ if (!match) {
24
+ throw new Error(`Task file at ${filePath} is missing YAML frontmatter.`);
25
+ }
26
+ const yamlPart = match[1] ?? '';
27
+ const body = match[2] ?? '';
28
+ const raw = parseYaml(yamlPart);
29
+ if (!isValidRawFrontmatter(raw)) {
30
+ throw new Error(`Task file at ${filePath} has invalid frontmatter.`);
31
+ }
32
+ const dependsOnRaw = raw['depends-on'];
33
+ return {
34
+ frontmatter: {
35
+ id: raw.id,
36
+ slug: raw.slug,
37
+ status: raw.status,
38
+ dependsOn: dependsOnRaw.filter((v) => typeof v === 'string'),
39
+ },
40
+ body,
41
+ };
42
+ }
43
+ export async function writeTaskFile(filePath, file) {
44
+ const rawFrontmatter = {
45
+ id: file.frontmatter.id,
46
+ slug: file.frontmatter.slug,
47
+ status: file.frontmatter.status,
48
+ };
49
+ rawFrontmatter['depends-on'] = file.frontmatter.dependsOn;
50
+ const content = `---\n${stringifyYaml(rawFrontmatter)}---\n${file.body}`;
51
+ await writeFile(filePath, content, 'utf8');
52
+ }
53
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../../../src/core/tasks/frontmatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { TaskFrontmatter } from './types.js';
2
+ export declare function listTasks(projectRoot: string, featureSlug: string): Promise<TaskFrontmatter[]>;
@@ -0,0 +1,18 @@
1
+ import { readdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { readTaskFile } from './frontmatter.js';
4
+ import { buildTasksDirectoryPath } from './paths.js';
5
+ function isMissingPathError(error) {
6
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
7
+ }
8
+ export async function listTasks(projectRoot, featureSlug) {
9
+ const tasksDir = buildTasksDirectoryPath(projectRoot, featureSlug);
10
+ const entries = await readdir(tasksDir, { withFileTypes: true }).catch((error) => {
11
+ if (isMissingPathError(error))
12
+ return [];
13
+ throw error;
14
+ });
15
+ const taskFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.md'));
16
+ return Promise.all(taskFiles.map((entry) => readTaskFile(join(tasksDir, entry.name)).then((file) => file.frontmatter)));
17
+ }
18
+ //# sourceMappingURL=list.js.map