@fission-ai/openspec 1.8.0 → 1.9.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 (48) hide show
  1. package/dist/cli/index.d.ts +15 -0
  2. package/dist/cli/index.js +33 -4
  3. package/dist/commands/change.js +1 -0
  4. package/dist/commands/config.d.ts +5 -0
  5. package/dist/commands/config.js +5 -1
  6. package/dist/commands/schema.js +193 -28
  7. package/dist/commands/validate.d.ts +23 -0
  8. package/dist/commands/validate.js +148 -4
  9. package/dist/commands/workflow/schemas.d.ts +2 -0
  10. package/dist/commands/workflow/schemas.js +9 -2
  11. package/dist/core/archive.js +10 -3
  12. package/dist/core/artifact-graph/resolver.d.ts +0 -12
  13. package/dist/core/artifact-graph/resolver.js +14 -0
  14. package/dist/core/command-generation/adapters/command-code.d.ts +19 -0
  15. package/dist/core/command-generation/adapters/command-code.js +34 -0
  16. package/dist/core/command-generation/adapters/index.d.ts +1 -0
  17. package/dist/core/command-generation/adapters/index.js +1 -0
  18. package/dist/core/command-generation/registry.js +2 -0
  19. package/dist/core/completions/command-registry.js +5 -0
  20. package/dist/core/config.d.ts +1 -0
  21. package/dist/core/config.js +16 -15
  22. package/dist/core/init.js +19 -7
  23. package/dist/core/legacy-cleanup.d.ts +10 -0
  24. package/dist/core/legacy-cleanup.js +48 -26
  25. package/dist/core/parsers/requirement-blocks.js +41 -9
  26. package/dist/core/parsers/requirement-text.d.ts +8 -0
  27. package/dist/core/parsers/requirement-text.js +4 -3
  28. package/dist/core/root-selection.d.ts +1 -1
  29. package/dist/core/shared-skill-target.d.ts +15 -0
  30. package/dist/core/shared-skill-target.js +30 -0
  31. package/dist/core/specs-apply.js +11 -4
  32. package/dist/core/templates/workflows/apply-change.js +3 -0
  33. package/dist/core/templates/workflows/propose.js +2 -2
  34. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  35. package/dist/core/templates/workflows/store-selection.js +1 -1
  36. package/dist/core/templates/workflows/update-change.js +4 -4
  37. package/dist/core/update.js +34 -7
  38. package/dist/core/validation/task-numbering.d.ts +16 -0
  39. package/dist/core/validation/task-numbering.js +57 -0
  40. package/dist/core/validation/validator.d.ts +5 -2
  41. package/dist/core/validation/validator.js +57 -3
  42. package/dist/telemetry/index.d.ts +3 -1
  43. package/dist/telemetry/index.js +7 -1
  44. package/dist/utils/interactive.d.ts +30 -1
  45. package/dist/utils/interactive.js +105 -2
  46. package/dist/utils/task-progress.d.ts +34 -1
  47. package/dist/utils/task-progress.js +52 -28
  48. package/package.json +1 -1
@@ -64,24 +64,43 @@ function findTrackedTasksArtifact(schema) {
64
64
  * when the schema cannot be resolved or no tracked-tasks artifact exists.
65
65
  * `resolveSchema` throws on an unresolvable/misnamed schema; we swallow that so
66
66
  * the caller falls back to a single top-level `tasks.md` and never crashes.
67
+ * A `schemaGlobCache`, when supplied, memoizes the schema-name → glob lookup for
68
+ * the duration of one run.
67
69
  */
68
- function resolveTrackedTasksGlob(changeDir, projectRoot) {
70
+ function resolveTrackedTasksGlob(changeDir, projectRoot, schemaGlobCache) {
69
71
  try {
70
72
  const schemaName = resolveSchemaForChange(changeDir, undefined, projectRoot);
73
+ if (schemaGlobCache?.has(schemaName))
74
+ return schemaGlobCache.get(schemaName);
71
75
  const schema = resolveSchema(schemaName, projectRoot);
72
- return findTrackedTasksArtifact(schema)?.generates;
76
+ const generates = findTrackedTasksArtifact(schema)?.generates;
77
+ schemaGlobCache?.set(schemaName, generates);
78
+ return generates;
73
79
  }
74
80
  catch {
75
81
  return undefined;
76
82
  }
77
83
  }
78
- async function countSingleTopLevelTasksFile(changeDir) {
79
- const tasksPath = path.join(changeDir, 'tasks.md');
84
+ /** Resolves the task files selected by the schema's apply tracking rule. */
85
+ export function resolveTaskFilesForChange(changeDir, projectRoot, schemaGlobCache) {
86
+ const generates = resolveTrackedTasksGlob(changeDir, projectRoot, schemaGlobCache);
87
+ return generates ? resolveArtifactOutputs(changeDir, generates) : [];
88
+ }
89
+ /**
90
+ * Reads one task file and counts its checkboxes. ENOENT (a glob file that
91
+ * vanished between resolve and read, or the absent single top-level `tasks.md`)
92
+ * means zero tasks, exactly as before. Any other error (permissions, I/O,
93
+ * ENOTDIR) is recorded in `unreadable` so a caller can surface it; the count
94
+ * still contributes zero, so existing callers see no change.
95
+ */
96
+ async function countTaskFile(file, unreadable) {
80
97
  try {
81
- const content = await fs.readFile(tasksPath, 'utf-8');
98
+ const content = await fs.readFile(file, 'utf-8');
82
99
  return countTasksFromContent(content);
83
100
  }
84
- catch {
101
+ catch (error) {
102
+ if (error?.code !== 'ENOENT')
103
+ unreadable.push(file);
85
104
  return { total: 0, completed: 0 };
86
105
  }
87
106
  }
@@ -92,31 +111,36 @@ async function countSingleTopLevelTasksFile(changeDir) {
92
111
  * artifact (`resolveArtifactOutputs`) — so progress is no longer blind to nested
93
112
  * `tasks.md` files (#1202). Falls back to a single top-level `tasks.md` (exactly
94
113
  * as before) when the schema is unresolvable, no tracked-tasks artifact is found,
95
- * or the glob matches no file. Never throws.
114
+ * or the glob matches no file. Also reports task files that exist but could not
115
+ * be read. Per-file read errors are captured (never thrown); the only throw path
116
+ * is a malformed/unsafe schema whose glob resolution rejects (path traversal or
117
+ * a linked-directory cycle in `resolveArtifactOutputs`). Pass `schemaGlobCache`
118
+ * to memoize schema→glob resolution across many changes in one run.
96
119
  */
97
- export async function getTaskProgressForChange(changesDir, changeName, projectRoot) {
120
+ export async function getTaskProgressDetailForChange(changesDir, changeName, projectRoot, schemaGlobCache) {
98
121
  const changeDir = path.join(changesDir, changeName);
99
- const generates = resolveTrackedTasksGlob(changeDir, projectRoot);
100
- if (generates) {
101
- const files = resolveArtifactOutputs(changeDir, generates);
102
- if (files.length > 0) {
103
- let total = 0;
104
- let completed = 0;
105
- for (const file of files) {
106
- try {
107
- const content = await fs.readFile(file, 'utf-8');
108
- const progress = countTasksFromContent(content);
109
- total += progress.total;
110
- completed += progress.completed;
111
- }
112
- catch {
113
- // Swallow files that vanish between glob and read, as before.
114
- }
115
- }
116
- return { total, completed };
117
- }
122
+ const files = resolveTaskFilesForChange(changeDir, projectRoot, schemaGlobCache);
123
+ const targets = files.length > 0 ? files : [path.join(changeDir, 'tasks.md')];
124
+ const unreadable = [];
125
+ let total = 0;
126
+ let completed = 0;
127
+ for (const file of targets) {
128
+ const progress = await countTaskFile(file, unreadable);
129
+ total += progress.total;
130
+ completed += progress.completed;
118
131
  }
119
- return countSingleTopLevelTasksFile(changeDir);
132
+ return { total, completed, unreadable };
133
+ }
134
+ /**
135
+ * The task-completion counter `status`, `list`, and `archive` share. Delegates
136
+ * to `getTaskProgressDetailForChange` and drops the `unreadable` detail, so its
137
+ * returned totals are unchanged. Throws only on the same malformed/unsafe-schema
138
+ * glob-resolution path as that function (existing behavior; callers guard it as
139
+ * they did before).
140
+ */
141
+ export async function getTaskProgressForChange(changesDir, changeName, projectRoot) {
142
+ const { total, completed } = await getTaskProgressDetailForChange(changesDir, changeName, projectRoot);
143
+ return { total, completed };
120
144
  }
121
145
  export function formatTaskStatus(progress) {
122
146
  if (progress.total === 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fission-ai/openspec",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "openspec",