@dedesfr/prompter 0.9.0 → 1.1.0

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 (225) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +105 -77
  3. package/dist/cli/index.js +25 -1
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +35 -9
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/login.d.ts +4 -0
  9. package/dist/commands/login.d.ts.map +1 -0
  10. package/dist/commands/login.js +56 -0
  11. package/dist/commands/login.js.map +1 -0
  12. package/dist/commands/logout.d.ts +4 -0
  13. package/dist/commands/logout.d.ts.map +1 -0
  14. package/dist/commands/logout.js +14 -0
  15. package/dist/commands/logout.js.map +1 -0
  16. package/dist/commands/update.d.ts +0 -2
  17. package/dist/commands/update.d.ts.map +1 -1
  18. package/dist/commands/update.js +19 -48
  19. package/dist/commands/update.js.map +1 -1
  20. package/dist/commands/whoami.d.ts +4 -0
  21. package/dist/commands/whoami.d.ts.map +1 -0
  22. package/dist/commands/whoami.js +42 -0
  23. package/dist/commands/whoami.js.map +1 -0
  24. package/dist/core/auth-store.d.ts +10 -0
  25. package/dist/core/auth-store.d.ts.map +1 -0
  26. package/dist/core/auth-store.js +39 -0
  27. package/dist/core/auth-store.js.map +1 -0
  28. package/dist/core/config.d.ts +0 -7
  29. package/dist/core/config.d.ts.map +1 -1
  30. package/dist/core/config.js +0 -128
  31. package/dist/core/config.js.map +1 -1
  32. package/dist/core/registry.d.ts +18 -0
  33. package/dist/core/registry.d.ts.map +1 -0
  34. package/dist/core/registry.js +94 -0
  35. package/dist/core/registry.js.map +1 -0
  36. package/package.json +7 -1
  37. package/AGENTS.md +0 -123
  38. package/CLAUDE.md +0 -17
  39. package/build.js +0 -20
  40. package/convex-setup.md +0 -403
  41. package/dist/core/prompt-templates.d.ts +0 -23
  42. package/dist/core/prompt-templates.d.ts.map +0 -1
  43. package/dist/core/prompt-templates.js +0 -3485
  44. package/dist/core/prompt-templates.js.map +0 -1
  45. package/prompt/ai-humanizer.md +0 -45
  46. package/prompt/api-contract-generator.md +0 -234
  47. package/prompt/apply.md +0 -17
  48. package/prompt/archive.md +0 -21
  49. package/prompt/design-system.md +0 -210
  50. package/prompt/document-explainer.md +0 -149
  51. package/prompt/epic-generator.md +0 -198
  52. package/prompt/epic-single.md +0 -47
  53. package/prompt/erd-generator.md +0 -130
  54. package/prompt/fsd-generator.md +0 -157
  55. package/prompt/prd-agent-generator.md +0 -147
  56. package/prompt/prd-generator.md +0 -195
  57. package/prompt/product-brief.md +0 -289
  58. package/prompt/proposal.md +0 -22
  59. package/prompt/qa-test-scenario.md +0 -133
  60. package/prompt/skill-creator.md +0 -350
  61. package/prompt/story-generator.md +0 -278
  62. package/prompt/story-single.md +0 -70
  63. package/prompt/tdd-generator.md +0 -294
  64. package/prompt/tdd-lite-generator.md +0 -224
  65. package/prompt/wireframe-generator.md +0 -219
  66. package/skills/ai-context-generator/SKILL.md +0 -54
  67. package/skills/ai-context-generator/references/AGENTS.template.md +0 -83
  68. package/skills/ai-context-generator/references/CLAUDE.template.md +0 -39
  69. package/skills/ai-context-generator/references/behavioral-guidelines.md +0 -71
  70. package/skills/ai-context-generator/references/discovery-checklist.md +0 -40
  71. package/skills/ai-context-generator/references/examples/AGENTS.good.md +0 -103
  72. package/skills/ai-context-generator/references/extraction-checklist.md +0 -23
  73. package/skills/ai-context-generator/references/overlays/laravel.md +0 -44
  74. package/skills/ai-humanizer/SKILL.md +0 -50
  75. package/skills/api-contract-generator/SKILL.md +0 -243
  76. package/skills/apply/SKILL.md +0 -23
  77. package/skills/archive/SKILL.md +0 -27
  78. package/skills/cerebro/SKILL.md +0 -187
  79. package/skills/cerebro/references/agents.md +0 -213
  80. package/skills/code-review/SKILL.md +0 -373
  81. package/skills/code-review/assets/report-template-agent.md +0 -212
  82. package/skills/code-review/assets/report-template-compact.md +0 -81
  83. package/skills/code-review/assets/report-template-full.md +0 -264
  84. package/skills/code-review/assets/report-template-human.md +0 -168
  85. package/skills/code-review/references/universal-patterns.md +0 -495
  86. package/skills/design-md/README.md +0 -34
  87. package/skills/design-md/SKILL.md +0 -172
  88. package/skills/design-md/examples/DESIGN.md +0 -154
  89. package/skills/design-system/SKILL.md +0 -216
  90. package/skills/design-system-generator/SKILL.md +0 -324
  91. package/skills/design-system-generator/assets/design-system-template.md +0 -348
  92. package/skills/design-system-generator/references/extraction-patterns.md +0 -321
  93. package/skills/doc-builder/SKILL.md +0 -115
  94. package/skills/doc-builder/references/ui-patterns.md +0 -394
  95. package/skills/document-explainer/SKILL.md +0 -155
  96. package/skills/document-translator/SKILL.md +0 -58
  97. package/skills/enhance/SKILL.md +0 -47
  98. package/skills/enhance-prompt/README.md +0 -34
  99. package/skills/enhance-prompt/SKILL.md +0 -204
  100. package/skills/enhance-prompt/references/KEYWORDS.md +0 -114
  101. package/skills/epic-generator/SKILL.md +0 -204
  102. package/skills/epic-single/SKILL.md +0 -63
  103. package/skills/erd-generator/SKILL.md +0 -138
  104. package/skills/feature-planner/SKILL.md +0 -305
  105. package/skills/feature-planner/assets/implementation-plan-template.md +0 -85
  106. package/skills/frontend-design/LICENSE.txt +0 -177
  107. package/skills/frontend-design/SKILL.md +0 -42
  108. package/skills/fsd-generator/SKILL.md +0 -163
  109. package/skills/gamma-builder/SKILL.md +0 -134
  110. package/skills/laravel-code-review/SKILL.md +0 -383
  111. package/skills/laravel-code-review/assets/report-template-agent.md +0 -195
  112. package/skills/laravel-code-review/assets/report-template-compact.md +0 -79
  113. package/skills/laravel-code-review/assets/report-template-full.md +0 -253
  114. package/skills/laravel-code-review/assets/report-template-human.md +0 -159
  115. package/skills/laravel-code-review/references/laravel-patterns.md +0 -571
  116. package/skills/laravel-code-review/references/php84-features.md +0 -442
  117. package/skills/mcp-builder/LICENSE.txt +0 -202
  118. package/skills/mcp-builder/SKILL.md +0 -236
  119. package/skills/mcp-builder/reference/evaluation.md +0 -602
  120. package/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  121. package/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  122. package/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  123. package/skills/mcp-builder/scripts/connections.py +0 -151
  124. package/skills/mcp-builder/scripts/evaluation.py +0 -373
  125. package/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  126. package/skills/mcp-builder/scripts/requirements.txt +0 -2
  127. package/skills/meeting-notes/SKILL.md +0 -159
  128. package/skills/meeting-notes/evals/evals.json +0 -23
  129. package/skills/prd-agent-generator/SKILL.md +0 -132
  130. package/skills/prd-generator/SKILL.md +0 -211
  131. package/skills/product-brief/SKILL.md +0 -141
  132. package/skills/project-orchestrator/SKILL.md +0 -487
  133. package/skills/project-orchestrator/assets/caddy-vps-setup.md +0 -180
  134. package/skills/project-orchestrator/assets/plan-summary-template.md +0 -159
  135. package/skills/prompter-specs/SKILL.md +0 -115
  136. package/skills/prompter-workflow/SKILL.md +0 -166
  137. package/skills/prompter-workflow/evals/evals.json +0 -89
  138. package/skills/proposal/SKILL.md +0 -28
  139. package/skills/qa-test-scenario/SKILL.md +0 -149
  140. package/skills/skill-creator/SKILL.md +0 -173
  141. package/skills/sph-generator/SKILL.md +0 -488
  142. package/skills/story-generator/SKILL.md +0 -285
  143. package/skills/story-single/SKILL.md +0 -86
  144. package/skills/tdd-generator/SKILL.md +0 -300
  145. package/skills/tdd-lite-generator/SKILL.md +0 -230
  146. package/skills/ui-ux-pro/SKILL.md +0 -199
  147. package/skills/ui-ux-pro/assets/design-spec-template.md +0 -173
  148. package/skills/ui-ux-pro/references/component-patterns.md +0 -255
  149. package/skills/ui-ux-pro/references/design-principles.md +0 -167
  150. package/skills/wireframe-generator/SKILL.md +0 -227
  151. package/src/cli/index.ts +0 -223
  152. package/src/commands/archive.ts +0 -302
  153. package/src/commands/change.ts +0 -292
  154. package/src/commands/config.ts +0 -233
  155. package/src/commands/guide.ts +0 -50
  156. package/src/commands/init.ts +0 -597
  157. package/src/commands/list.ts +0 -194
  158. package/src/commands/show.ts +0 -138
  159. package/src/commands/spec.ts +0 -251
  160. package/src/commands/update.ts +0 -129
  161. package/src/commands/upgrade.ts +0 -30
  162. package/src/commands/validate.ts +0 -326
  163. package/src/core/artifact-graph/graph.ts +0 -167
  164. package/src/core/artifact-graph/index.ts +0 -44
  165. package/src/core/artifact-graph/instruction-loader.ts +0 -302
  166. package/src/core/artifact-graph/resolver.ts +0 -226
  167. package/src/core/artifact-graph/schema.ts +0 -124
  168. package/src/core/artifact-graph/state.ts +0 -64
  169. package/src/core/artifact-graph/types.ts +0 -65
  170. package/src/core/completions/command-registry.ts +0 -382
  171. package/src/core/completions/completion-provider.ts +0 -128
  172. package/src/core/completions/generators/bash-generator.ts +0 -191
  173. package/src/core/completions/generators/fish-generator.ts +0 -188
  174. package/src/core/completions/generators/powershell-generator.ts +0 -223
  175. package/src/core/completions/generators/zsh-generator.ts +0 -281
  176. package/src/core/completions/templates/bash-templates.ts +0 -24
  177. package/src/core/completions/templates/fish-templates.ts +0 -40
  178. package/src/core/completions/templates/powershell-templates.ts +0 -25
  179. package/src/core/completions/templates/zsh-templates.ts +0 -36
  180. package/src/core/completions/types.ts +0 -90
  181. package/src/core/config-schema.ts +0 -230
  182. package/src/core/config.ts +0 -181
  183. package/src/core/configurators/slash/antigravity.ts +0 -10
  184. package/src/core/configurators/slash/base.ts +0 -109
  185. package/src/core/configurators/slash/claude.ts +0 -10
  186. package/src/core/configurators/slash/codex.ts +0 -10
  187. package/src/core/configurators/slash/droid.ts +0 -10
  188. package/src/core/configurators/slash/forge.ts +0 -10
  189. package/src/core/configurators/slash/github-copilot.ts +0 -10
  190. package/src/core/configurators/slash/index.ts +0 -10
  191. package/src/core/configurators/slash/kilocode.ts +0 -10
  192. package/src/core/configurators/slash/opencode.ts +0 -10
  193. package/src/core/configurators/slash/registry.ts +0 -51
  194. package/src/core/converters/json-converter.ts +0 -62
  195. package/src/core/global-config.ts +0 -136
  196. package/src/core/parsers/change-parser.ts +0 -234
  197. package/src/core/parsers/markdown-parser.ts +0 -237
  198. package/src/core/parsers/requirement-blocks.ts +0 -234
  199. package/src/core/prompt-templates.ts +0 -3504
  200. package/src/core/schemas/base.schema.ts +0 -20
  201. package/src/core/schemas/change.schema.ts +0 -42
  202. package/src/core/schemas/index.ts +0 -20
  203. package/src/core/schemas/spec.schema.ts +0 -17
  204. package/src/core/skill-discovery.ts +0 -68
  205. package/src/core/specs-apply.ts +0 -483
  206. package/src/core/styles/palette.ts +0 -8
  207. package/src/core/templates/agents-template.ts +0 -459
  208. package/src/core/templates/claude-template.ts +0 -2
  209. package/src/core/templates/index.ts +0 -3
  210. package/src/core/templates/project-template.ts +0 -32
  211. package/src/core/validation/constants.ts +0 -48
  212. package/src/core/validation/types.ts +0 -19
  213. package/src/core/validation/validator.ts +0 -449
  214. package/src/core/view.ts +0 -219
  215. package/src/index.ts +0 -1
  216. package/src/utils/change-metadata.ts +0 -171
  217. package/src/utils/change-utils.ts +0 -131
  218. package/src/utils/file-system.ts +0 -252
  219. package/src/utils/index.ts +0 -12
  220. package/src/utils/interactive.ts +0 -29
  221. package/src/utils/item-discovery.ts +0 -66
  222. package/src/utils/match.ts +0 -26
  223. package/src/utils/shell-detection.ts +0 -62
  224. package/src/utils/task-progress.ts +0 -43
  225. package/tsconfig.json +0 -28
@@ -1,171 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import * as yaml from 'yaml';
4
- import { ChangeMetadataSchema, type ChangeMetadata } from '../core/artifact-graph/types.js';
5
- import { listSchemas } from '../core/artifact-graph/resolver.js';
6
-
7
- const METADATA_FILENAME = '.prompter.yaml';
8
-
9
- /**
10
- * Error thrown when change metadata validation fails.
11
- */
12
- export class ChangeMetadataError extends Error {
13
- constructor(
14
- message: string,
15
- public readonly metadataPath: string,
16
- public readonly cause?: Error
17
- ) {
18
- super(message);
19
- this.name = 'ChangeMetadataError';
20
- }
21
- }
22
-
23
- /**
24
- * Validates that a schema name is valid (exists in available schemas).
25
- *
26
- * @param schemaName - The schema name to validate
27
- * @returns The validated schema name
28
- * @throws Error if schema is not found
29
- */
30
- export function validateSchemaName(schemaName: string): string {
31
- const availableSchemas = listSchemas();
32
- if (!availableSchemas.includes(schemaName)) {
33
- throw new Error(
34
- `Unknown schema '${schemaName}'. Available: ${availableSchemas.join(', ')}`
35
- );
36
- }
37
- return schemaName;
38
- }
39
-
40
- /**
41
- * Writes change metadata to .prompter.yaml in the change directory.
42
- *
43
- * @param changeDir - The path to the change directory
44
- * @param metadata - The metadata to write
45
- * @throws ChangeMetadataError if validation fails or write fails
46
- */
47
- export function writeChangeMetadata(
48
- changeDir: string,
49
- metadata: ChangeMetadata
50
- ): void {
51
- const metaPath = path.join(changeDir, METADATA_FILENAME);
52
-
53
- // Validate schema exists
54
- validateSchemaName(metadata.schema);
55
-
56
- // Validate with Zod
57
- const parseResult = ChangeMetadataSchema.safeParse(metadata);
58
- if (!parseResult.success) {
59
- throw new ChangeMetadataError(
60
- `Invalid metadata: ${parseResult.error.message}`,
61
- metaPath
62
- );
63
- }
64
-
65
- // Write YAML file
66
- const content = yaml.stringify(parseResult.data);
67
- try {
68
- fs.writeFileSync(metaPath, content, 'utf-8');
69
- } catch (err) {
70
- const ioError = err instanceof Error ? err : new Error(String(err));
71
- throw new ChangeMetadataError(
72
- `Failed to write metadata: ${ioError.message}`,
73
- metaPath,
74
- ioError
75
- );
76
- }
77
- }
78
-
79
- /**
80
- * Reads change metadata from .prompter.yaml in the change directory.
81
- *
82
- * @param changeDir - The path to the change directory
83
- * @returns The validated metadata, or null if no metadata file exists
84
- * @throws ChangeMetadataError if the file exists but is invalid
85
- */
86
- export function readChangeMetadata(changeDir: string): ChangeMetadata | null {
87
- const metaPath = path.join(changeDir, METADATA_FILENAME);
88
-
89
- if (!fs.existsSync(metaPath)) {
90
- return null;
91
- }
92
-
93
- let content: string;
94
- try {
95
- content = fs.readFileSync(metaPath, 'utf-8');
96
- } catch (err) {
97
- const ioError = err instanceof Error ? err : new Error(String(err));
98
- throw new ChangeMetadataError(
99
- `Failed to read metadata: ${ioError.message}`,
100
- metaPath,
101
- ioError
102
- );
103
- }
104
-
105
- let parsed: unknown;
106
- try {
107
- parsed = yaml.parse(content);
108
- } catch (err) {
109
- const parseError = err instanceof Error ? err : new Error(String(err));
110
- throw new ChangeMetadataError(
111
- `Invalid YAML in metadata file: ${parseError.message}`,
112
- metaPath,
113
- parseError
114
- );
115
- }
116
-
117
- // Validate with Zod
118
- const parseResult = ChangeMetadataSchema.safeParse(parsed);
119
- if (!parseResult.success) {
120
- throw new ChangeMetadataError(
121
- `Invalid metadata: ${parseResult.error.message}`,
122
- metaPath
123
- );
124
- }
125
-
126
- // Validate that the schema exists
127
- const availableSchemas = listSchemas();
128
- if (!availableSchemas.includes(parseResult.data.schema)) {
129
- throw new ChangeMetadataError(
130
- `Unknown schema '${parseResult.data.schema}'. Available: ${availableSchemas.join(', ')}`,
131
- metaPath
132
- );
133
- }
134
-
135
- return parseResult.data;
136
- }
137
-
138
- /**
139
- * Resolves the schema for a change, with explicit override taking precedence.
140
- *
141
- * Resolution order:
142
- * 1. Explicit schema (if provided)
143
- * 2. Schema from .prompter.yaml metadata (if exists)
144
- * 3. Default 'spec-driven'
145
- *
146
- * @param changeDir - The path to the change directory
147
- * @param explicitSchema - Optional explicit schema override
148
- * @returns The resolved schema name
149
- */
150
- export function resolveSchemaForChange(
151
- changeDir: string,
152
- explicitSchema?: string
153
- ): string {
154
- // 1. Explicit override wins
155
- if (explicitSchema) {
156
- return explicitSchema;
157
- }
158
-
159
- // 2. Try reading from metadata
160
- try {
161
- const metadata = readChangeMetadata(changeDir);
162
- if (metadata?.schema) {
163
- return metadata.schema;
164
- }
165
- } catch {
166
- // If metadata read fails, fall back to default
167
- }
168
-
169
- // 3. Default
170
- return 'spec-driven';
171
- }
@@ -1,131 +0,0 @@
1
- import path from 'path';
2
- import { FileSystemUtils } from './file-system.js';
3
- import { writeChangeMetadata, validateSchemaName } from './change-metadata.js';
4
-
5
- const DEFAULT_SCHEMA = 'spec-driven';
6
-
7
- /**
8
- * Options for creating a change.
9
- */
10
- export interface CreateChangeOptions {
11
- /** The workflow schema to use (default: 'spec-driven') */
12
- schema?: string;
13
- }
14
-
15
- /**
16
- * Result of validating a change name.
17
- */
18
- export interface ValidationResult {
19
- valid: boolean;
20
- error?: string;
21
- }
22
-
23
- /**
24
- * Validates that a change name follows kebab-case conventions.
25
- *
26
- * Valid names:
27
- * - Start with a lowercase letter
28
- * - Contain only lowercase letters, numbers, and hyphens
29
- * - Do not start or end with a hyphen
30
- * - Do not contain consecutive hyphens
31
- *
32
- * @param name - The change name to validate
33
- * @returns Validation result with `valid: true` or `valid: false` with an error message
34
- *
35
- * @example
36
- * validateChangeName('add-auth') // { valid: true }
37
- * validateChangeName('Add-Auth') // { valid: false, error: '...' }
38
- */
39
- export function validateChangeName(name: string): ValidationResult {
40
- // Pattern: starts with lowercase letter, followed by lowercase letters/numbers,
41
- // optionally followed by hyphen + lowercase letters/numbers (repeatable)
42
- const kebabCasePattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
43
-
44
- if (!name) {
45
- return { valid: false, error: 'Change name cannot be empty' };
46
- }
47
-
48
- if (!kebabCasePattern.test(name)) {
49
- // Provide specific error messages for common mistakes
50
- if (/[A-Z]/.test(name)) {
51
- return { valid: false, error: 'Change name must be lowercase (use kebab-case)' };
52
- }
53
- if (/\s/.test(name)) {
54
- return { valid: false, error: 'Change name cannot contain spaces (use hyphens instead)' };
55
- }
56
- if (/_/.test(name)) {
57
- return { valid: false, error: 'Change name cannot contain underscores (use hyphens instead)' };
58
- }
59
- if (name.startsWith('-')) {
60
- return { valid: false, error: 'Change name cannot start with a hyphen' };
61
- }
62
- if (name.endsWith('-')) {
63
- return { valid: false, error: 'Change name cannot end with a hyphen' };
64
- }
65
- if (/--/.test(name)) {
66
- return { valid: false, error: 'Change name cannot contain consecutive hyphens' };
67
- }
68
- if (/[^a-z0-9-]/.test(name)) {
69
- return { valid: false, error: 'Change name can only contain lowercase letters, numbers, and hyphens' };
70
- }
71
- if (/^[0-9]/.test(name)) {
72
- return { valid: false, error: 'Change name must start with a letter' };
73
- }
74
-
75
- return { valid: false, error: 'Change name must follow kebab-case convention (e.g., add-auth, refactor-db)' };
76
- }
77
-
78
- return { valid: true };
79
- }
80
-
81
- /**
82
- * Creates a new change directory with metadata file.
83
- *
84
- * @param projectRoot - The root directory of the project (where `prompter/` lives)
85
- * @param name - The change name (must be valid kebab-case)
86
- * @param options - Optional settings for the change
87
- * @throws Error if the change name is invalid
88
- * @throws Error if the schema name is invalid
89
- * @throws Error if the change directory already exists
90
- *
91
- * @example
92
- * // Creates prompter/changes/add-auth/ with default schema
93
- * await createChange('/path/to/project', 'add-auth')
94
- *
95
- * @example
96
- * // Creates prompter/changes/add-auth/ with TDD schema
97
- * await createChange('/path/to/project', 'add-auth', { schema: 'tdd' })
98
- */
99
- export async function createChange(
100
- projectRoot: string,
101
- name: string,
102
- options: CreateChangeOptions = {}
103
- ): Promise<void> {
104
- // Validate the name first
105
- const validation = validateChangeName(name);
106
- if (!validation.valid) {
107
- throw new Error(validation.error);
108
- }
109
-
110
- // Determine schema (validate if provided)
111
- const schemaName = options.schema ?? DEFAULT_SCHEMA;
112
- validateSchemaName(schemaName);
113
-
114
- // Build the change directory path
115
- const changeDir = path.join(projectRoot, 'prompter', 'changes', name);
116
-
117
- // Check if change already exists
118
- if (await FileSystemUtils.directoryExists(changeDir)) {
119
- throw new Error(`Change '${name}' already exists at ${changeDir}`);
120
- }
121
-
122
- // Create the directory (including parent directories if needed)
123
- await FileSystemUtils.createDirectory(changeDir);
124
-
125
- // Write metadata file with schema and creation date
126
- const today = new Date().toISOString().split('T')[0];
127
- writeChangeMetadata(changeDir, {
128
- schema: schemaName,
129
- created: today,
130
- });
131
- }
@@ -1,252 +0,0 @@
1
- import { promises as fs, constants as fsConstants } from 'fs';
2
- import path from 'path';
3
-
4
- function isMarkerOnOwnLine(content: string, markerIndex: number, markerLength: number): boolean {
5
- let leftIndex = markerIndex - 1;
6
- while (leftIndex >= 0 && content[leftIndex] !== '\n') {
7
- const char = content[leftIndex];
8
- if (char !== ' ' && char !== '\t' && char !== '\r') {
9
- return false;
10
- }
11
- leftIndex--;
12
- }
13
-
14
- let rightIndex = markerIndex + markerLength;
15
- while (rightIndex < content.length && content[rightIndex] !== '\n') {
16
- const char = content[rightIndex];
17
- if (char !== ' ' && char !== '\t' && char !== '\r') {
18
- return false;
19
- }
20
- rightIndex++;
21
- }
22
-
23
- return true;
24
- }
25
-
26
- function findMarkerIndex(
27
- content: string,
28
- marker: string,
29
- fromIndex = 0
30
- ): number {
31
- let currentIndex = content.indexOf(marker, fromIndex);
32
-
33
- while (currentIndex !== -1) {
34
- if (isMarkerOnOwnLine(content, currentIndex, marker.length)) {
35
- return currentIndex;
36
- }
37
-
38
- currentIndex = content.indexOf(marker, currentIndex + marker.length);
39
- }
40
-
41
- return -1;
42
- }
43
-
44
- export class FileSystemUtils {
45
- /**
46
- * Converts a path to use forward slashes (POSIX style).
47
- * Essential for cross-platform compatibility with glob libraries like fast-glob.
48
- */
49
- static toPosixPath(p: string): string {
50
- return p.replace(/\\/g, '/');
51
- }
52
-
53
- private static isWindowsBasePath(basePath: string): boolean {
54
- return /^[A-Za-z]:[\\/]/.test(basePath) || basePath.startsWith('\\');
55
- }
56
-
57
- private static normalizeSegments(segments: string[]): string[] {
58
- return segments
59
- .flatMap((segment) => segment.split(/[\\/]+/u))
60
- .filter((part) => part.length > 0);
61
- }
62
-
63
- static joinPath(basePath: string, ...segments: string[]): string {
64
- const normalizedSegments = this.normalizeSegments(segments);
65
-
66
- if (this.isWindowsBasePath(basePath)) {
67
- const normalizedBasePath = path.win32.normalize(basePath);
68
- return normalizedSegments.length
69
- ? path.win32.join(normalizedBasePath, ...normalizedSegments)
70
- : normalizedBasePath;
71
- }
72
-
73
- const posixBasePath = basePath.replace(/\\/g, '/');
74
-
75
- return normalizedSegments.length
76
- ? path.posix.join(posixBasePath, ...normalizedSegments)
77
- : path.posix.normalize(posixBasePath);
78
- }
79
-
80
- static async createDirectory(dirPath: string): Promise<void> {
81
- await fs.mkdir(dirPath, { recursive: true });
82
- }
83
-
84
- static async fileExists(filePath: string): Promise<boolean> {
85
- try {
86
- await fs.access(filePath);
87
- return true;
88
- } catch (error: any) {
89
- if (error.code !== 'ENOENT') {
90
- console.debug(`Unable to check if file exists at ${filePath}: ${error.message}`);
91
- }
92
- return false;
93
- }
94
- }
95
-
96
- /**
97
- * Finds the first existing parent directory by walking up the directory tree.
98
- * @param dirPath Starting directory path
99
- * @returns The first existing directory path, or null if root is reached without finding one
100
- */
101
- private static async findFirstExistingDirectory(dirPath: string): Promise<string | null> {
102
- let currentDir = dirPath;
103
-
104
- while (true) {
105
- try {
106
- const stats = await fs.stat(currentDir);
107
- if (stats.isDirectory()) {
108
- return currentDir;
109
- }
110
- // Path component exists but is not a directory (edge case)
111
- console.debug(`Path component ${currentDir} exists but is not a directory`);
112
- return null;
113
- } catch (error: any) {
114
- if (error.code === 'ENOENT') {
115
- // Directory doesn't exist, move up one level
116
- const parentDir = path.dirname(currentDir);
117
- if (parentDir === currentDir) {
118
- // Reached filesystem root without finding existing directory
119
- return null;
120
- }
121
- currentDir = parentDir;
122
- } else {
123
- // Unexpected error (permissions, I/O error, etc.)
124
- console.debug(`Error checking directory ${currentDir}: ${error.message}`);
125
- return null;
126
- }
127
- }
128
- }
129
- }
130
-
131
- static async canWriteFile(filePath: string): Promise<boolean> {
132
- try {
133
- const stats = await fs.stat(filePath);
134
-
135
- if (!stats.isFile()) {
136
- return true;
137
- }
138
-
139
- // On Windows, stats.mode doesn't reliably indicate write permissions.
140
- // Use fs.access with W_OK to check actual write permissions cross-platform.
141
- try {
142
- await fs.access(filePath, fsConstants.W_OK);
143
- return true;
144
- } catch {
145
- return false;
146
- }
147
- } catch (error: any) {
148
- if (error.code === 'ENOENT') {
149
- // File doesn't exist - find first existing parent directory and check its permissions
150
- const parentDir = path.dirname(filePath);
151
- const existingDir = await this.findFirstExistingDirectory(parentDir);
152
-
153
- if (existingDir === null) {
154
- // No existing parent directory found (edge case)
155
- return false;
156
- }
157
-
158
- // Check if the existing parent directory is writable
159
- try {
160
- await fs.access(existingDir, fsConstants.W_OK);
161
- return true;
162
- } catch {
163
- return false;
164
- }
165
- }
166
-
167
- console.debug(`Unable to determine write permissions for ${filePath}: ${error.message}`);
168
- return false;
169
- }
170
- }
171
-
172
- static async directoryExists(dirPath: string): Promise<boolean> {
173
- try {
174
- const stats = await fs.stat(dirPath);
175
- return stats.isDirectory();
176
- } catch (error: any) {
177
- if (error.code !== 'ENOENT') {
178
- console.debug(`Unable to check if directory exists at ${dirPath}: ${error.message}`);
179
- }
180
- return false;
181
- }
182
- }
183
-
184
- static async writeFile(filePath: string, content: string): Promise<void> {
185
- const dir = path.dirname(filePath);
186
- await this.createDirectory(dir);
187
- await fs.writeFile(filePath, content, 'utf-8');
188
- }
189
-
190
- static async readFile(filePath: string): Promise<string> {
191
- return await fs.readFile(filePath, 'utf-8');
192
- }
193
-
194
- static async updateFileWithMarkers(
195
- filePath: string,
196
- content: string,
197
- startMarker: string,
198
- endMarker: string
199
- ): Promise<void> {
200
- let existingContent = '';
201
-
202
- if (await this.fileExists(filePath)) {
203
- existingContent = await this.readFile(filePath);
204
-
205
- const startIndex = findMarkerIndex(existingContent, startMarker);
206
- const endIndex = startIndex !== -1
207
- ? findMarkerIndex(existingContent, endMarker, startIndex + startMarker.length)
208
- : findMarkerIndex(existingContent, endMarker);
209
-
210
- if (startIndex !== -1 && endIndex !== -1) {
211
- if (endIndex < startIndex) {
212
- throw new Error(
213
- `Invalid marker state in ${filePath}. End marker appears before start marker.`
214
- );
215
- }
216
-
217
- const before = existingContent.substring(0, startIndex);
218
- const after = existingContent.substring(endIndex + endMarker.length);
219
- existingContent = before + startMarker + '\n' + content + '\n' + endMarker + after;
220
- } else if (startIndex === -1 && endIndex === -1) {
221
- existingContent = startMarker + '\n' + content + '\n' + endMarker + '\n\n' + existingContent;
222
- } else {
223
- throw new Error(`Invalid marker state in ${filePath}. Found start: ${startIndex !== -1}, Found end: ${endIndex !== -1}`);
224
- }
225
- } else {
226
- existingContent = startMarker + '\n' + content + '\n' + endMarker;
227
- }
228
-
229
- await this.writeFile(filePath, existingContent);
230
- }
231
-
232
- static async ensureWritePermissions(dirPath: string): Promise<boolean> {
233
- try {
234
- // If directory doesn't exist, check parent directory permissions
235
- if (!await this.directoryExists(dirPath)) {
236
- const parentDir = path.dirname(dirPath);
237
- if (!await this.directoryExists(parentDir)) {
238
- await this.createDirectory(parentDir);
239
- }
240
- return await this.ensureWritePermissions(parentDir);
241
- }
242
-
243
- const testFile = path.join(dirPath, '.prompter-test-' + Date.now());
244
- await fs.writeFile(testFile, '');
245
- await fs.unlink(testFile);
246
- return true;
247
- } catch (error: any) {
248
- console.debug(`Insufficient permissions to write to ${dirPath}: ${error.message}`);
249
- return false;
250
- }
251
- }
252
- }
@@ -1,12 +0,0 @@
1
- // Shared utilities
2
- export { validateChangeName, createChange } from './change-utils.js';
3
- export type { ValidationResult, CreateChangeOptions } from './change-utils.js';
4
-
5
- // Change metadata utilities
6
- export {
7
- readChangeMetadata,
8
- writeChangeMetadata,
9
- resolveSchemaForChange,
10
- validateSchemaName,
11
- ChangeMetadataError,
12
- } from './change-metadata.js';
@@ -1,29 +0,0 @@
1
- export type InteractiveOptions = {
2
- /**
3
- * Explicit "disable prompts" flag passed by internal callers.
4
- */
5
- noInteractive?: boolean;
6
- /**
7
- * Commander-style negated option: `--no-interactive` sets this to false.
8
- */
9
- interactive?: boolean;
10
- };
11
-
12
- /**
13
- * Resolves whether non-interactive mode is requested.
14
- * Handles both explicit `noInteractive: true` and Commander.js style `interactive: false`.
15
- * Use this helper instead of manually checking options.noInteractive to avoid bugs.
16
- */
17
- export function resolveNoInteractive(value?: boolean | InteractiveOptions): boolean {
18
- if (typeof value === 'boolean') return value;
19
- return value?.noInteractive === true || value?.interactive === false;
20
- }
21
-
22
- export function isInteractive(value?: boolean | InteractiveOptions): boolean {
23
- if (resolveNoInteractive(value)) return false;
24
- if (process.env.OPEN_SPEC_INTERACTIVE === '0') return false;
25
- // Respect the standard CI environment variable (set by GitHub Actions, GitLab CI, Travis, etc.)
26
- if ('CI' in process.env) return false;
27
- return !!process.stdin.isTTY;
28
- }
29
-
@@ -1,66 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import path from 'path';
3
-
4
- export async function getActiveChangeIds(root: string = process.cwd()): Promise<string[]> {
5
- const changesPath = path.join(root, 'prompter', 'changes');
6
- try {
7
- const entries = await fs.readdir(changesPath, { withFileTypes: true });
8
- const result: string[] = [];
9
- for (const entry of entries) {
10
- if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'archive') continue;
11
- const proposalPath = path.join(changesPath, entry.name, 'proposal.md');
12
- try {
13
- await fs.access(proposalPath);
14
- result.push(entry.name);
15
- } catch {
16
- // skip directories without proposal.md
17
- }
18
- }
19
- return result.sort();
20
- } catch {
21
- return [];
22
- }
23
- }
24
-
25
- export async function getSpecIds(root: string = process.cwd()): Promise<string[]> {
26
- const specsPath = path.join(root, 'prompter', 'specs');
27
- const result: string[] = [];
28
- try {
29
- const entries = await fs.readdir(specsPath, { withFileTypes: true });
30
- for (const entry of entries) {
31
- if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
32
- const specFile = path.join(specsPath, entry.name, 'spec.md');
33
- try {
34
- await fs.access(specFile);
35
- result.push(entry.name);
36
- } catch {
37
- // ignore
38
- }
39
- }
40
- } catch {
41
- // ignore
42
- }
43
- return result.sort();
44
- }
45
-
46
- export async function getArchivedChangeIds(root: string = process.cwd()): Promise<string[]> {
47
- const archivePath = path.join(root, 'prompter', 'changes', 'archive');
48
- try {
49
- const entries = await fs.readdir(archivePath, { withFileTypes: true });
50
- const result: string[] = [];
51
- for (const entry of entries) {
52
- if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
53
- const proposalPath = path.join(archivePath, entry.name, 'proposal.md');
54
- try {
55
- await fs.access(proposalPath);
56
- result.push(entry.name);
57
- } catch {
58
- // skip directories without proposal.md
59
- }
60
- }
61
- return result.sort();
62
- } catch {
63
- return [];
64
- }
65
- }
66
-
@@ -1,26 +0,0 @@
1
- export function nearestMatches(input: string, candidates: string[], max: number = 5): string[] {
2
- const scored = candidates.map(candidate => ({ candidate, distance: levenshtein(input, candidate) }));
3
- scored.sort((a, b) => a.distance - b.distance);
4
- return scored.slice(0, max).map(s => s.candidate);
5
- }
6
-
7
- export function levenshtein(a: string, b: string): number {
8
- const m = a.length;
9
- const n = b.length;
10
- const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
11
- for (let i = 0; i <= m; i++) dp[i][0] = i;
12
- for (let j = 0; j <= n; j++) dp[0][j] = j;
13
- for (let i = 1; i <= m; i++) {
14
- for (let j = 1; j <= n; j++) {
15
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
16
- dp[i][j] = Math.min(
17
- dp[i - 1][j] + 1,
18
- dp[i][j - 1] + 1,
19
- dp[i - 1][j - 1] + cost
20
- );
21
- }
22
- }
23
- return dp[m][n];
24
- }
25
-
26
-