@polymorphism-tech/morph-spec 1.0.4 → 2.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 (152) hide show
  1. package/CLAUDE.md +1381 -0
  2. package/LICENSE +72 -0
  3. package/README.md +89 -6
  4. package/bin/detect-agents.js +225 -0
  5. package/bin/morph-spec.js +120 -0
  6. package/bin/render-template.js +302 -0
  7. package/bin/semantic-detect-agents.js +246 -0
  8. package/bin/validate-agents-skills.js +239 -0
  9. package/bin/validate-agents.js +69 -0
  10. package/bin/validate-phase.js +263 -0
  11. package/content/.azure/README.md +293 -0
  12. package/content/.azure/docs/azure-devops-setup.md +454 -0
  13. package/content/.azure/docs/branch-strategy.md +398 -0
  14. package/content/.azure/docs/local-development.md +515 -0
  15. package/content/.azure/pipelines/pipeline-variables.yml +34 -0
  16. package/content/.azure/pipelines/prod-pipeline.yml +319 -0
  17. package/content/.azure/pipelines/staging-pipeline.yml +234 -0
  18. package/content/.azure/pipelines/templates/build-dotnet.yml +75 -0
  19. package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -0
  20. package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -0
  21. package/content/.azure/pipelines/templates/infra-deploy.yml +90 -0
  22. package/content/.claude/commands/morph-apply.md +118 -26
  23. package/content/.claude/commands/morph-archive.md +9 -9
  24. package/content/.claude/commands/morph-clarify.md +184 -0
  25. package/content/.claude/commands/morph-design.md +275 -0
  26. package/content/.claude/commands/morph-proposal.md +56 -15
  27. package/content/.claude/commands/morph-setup.md +100 -0
  28. package/content/.claude/commands/morph-status.md +47 -32
  29. package/content/.claude/commands/morph-tasks.md +319 -0
  30. package/content/.claude/commands/morph-uiux.md +211 -0
  31. package/content/.claude/skills/specialists/ai-system-architect.md +604 -0
  32. package/content/.claude/skills/specialists/ms-agent-expert.md +143 -89
  33. package/content/.claude/skills/specialists/ui-ux-designer.md +744 -9
  34. package/content/.claude/skills/stacks/dotnet-blazor.md +244 -8
  35. package/content/.claude/skills/stacks/dotnet-nextjs.md +2 -2
  36. package/content/.morph/.morphversion +5 -0
  37. package/content/.morph/config/agents.json +101 -8
  38. package/content/.morph/config/azure-pricing.json +70 -0
  39. package/content/.morph/config/azure-pricing.schema.json +50 -0
  40. package/content/.morph/config/config.template.json +15 -3
  41. package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -0
  42. package/content/.morph/hooks/README.md +239 -0
  43. package/content/.morph/hooks/pre-commit-agents.sh +24 -0
  44. package/content/.morph/hooks/pre-commit-all.sh +48 -0
  45. package/content/.morph/hooks/pre-commit-costs.sh +91 -0
  46. package/content/.morph/hooks/pre-commit-specs.sh +49 -0
  47. package/content/.morph/hooks/pre-commit-tests.sh +60 -0
  48. package/content/.morph/project.md +5 -4
  49. package/content/.morph/schemas/agent.schema.json +296 -0
  50. package/content/.morph/standards/agent-framework-setup.md +453 -0
  51. package/content/.morph/standards/architecture.md +142 -7
  52. package/content/.morph/standards/azure.md +218 -23
  53. package/content/.morph/standards/coding.md +47 -12
  54. package/content/.morph/standards/dotnet10-migration.md +494 -0
  55. package/content/.morph/standards/fluent-ui-setup.md +590 -0
  56. package/content/.morph/standards/migration-guide.md +514 -0
  57. package/content/.morph/standards/passkeys-auth.md +423 -0
  58. package/content/.morph/standards/vector-search-rag.md +536 -0
  59. package/content/.morph/state.json +18 -0
  60. package/content/.morph/templates/FluentDesignTheme.cs +149 -0
  61. package/content/.morph/templates/MudTheme.cs +281 -0
  62. package/content/.morph/templates/contracts.cs +55 -55
  63. package/content/.morph/templates/decisions.md +4 -4
  64. package/content/.morph/templates/design-system.css +226 -0
  65. package/content/.morph/templates/infra/.dockerignore.example +89 -0
  66. package/content/.morph/templates/infra/Dockerfile.example +82 -0
  67. package/content/.morph/templates/infra/README.md +286 -0
  68. package/content/.morph/templates/infra/app-service.bicep +164 -0
  69. package/content/.morph/templates/infra/deploy.ps1 +229 -0
  70. package/content/.morph/templates/infra/deploy.sh +208 -0
  71. package/content/.morph/templates/infra/main.bicep +41 -7
  72. package/content/.morph/templates/infra/parameters.dev.json +6 -0
  73. package/content/.morph/templates/infra/parameters.prod.json +6 -0
  74. package/content/.morph/templates/infra/parameters.staging.json +29 -0
  75. package/content/.morph/templates/proposal.md +3 -3
  76. package/content/.morph/templates/recap.md +3 -3
  77. package/content/.morph/templates/spec.md +9 -8
  78. package/content/.morph/templates/sprint-status.yaml +68 -0
  79. package/content/.morph/templates/state.template.json +222 -0
  80. package/content/.morph/templates/story.md +143 -0
  81. package/content/.morph/templates/tasks.md +1 -1
  82. package/content/.morph/templates/ui-components.md +276 -0
  83. package/content/.morph/templates/ui-design-system.md +286 -0
  84. package/content/.morph/templates/ui-flows.md +336 -0
  85. package/content/.morph/templates/ui-mockups.md +133 -0
  86. package/content/.morph/test-infra/example.bicep +59 -0
  87. package/content/CLAUDE.md +124 -0
  88. package/content/README.md +79 -0
  89. package/detectors/config-detector.js +223 -0
  90. package/detectors/conversation-analyzer.js +163 -0
  91. package/detectors/index.js +84 -0
  92. package/detectors/standards-generator.js +275 -0
  93. package/detectors/structure-detector.js +221 -0
  94. package/docs/README.md +149 -0
  95. package/docs/api/cost-calculator.js.html +513 -0
  96. package/docs/api/design-system-generator.js.html +382 -0
  97. package/docs/api/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  98. package/docs/api/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  99. package/docs/api/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  100. package/docs/api/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  101. package/docs/api/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  102. package/docs/api/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  103. package/docs/api/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  104. package/docs/api/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  105. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  106. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  107. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  108. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  109. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  110. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  111. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  112. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  113. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  114. package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  115. package/docs/api/global.html +5263 -0
  116. package/docs/api/index.html +96 -0
  117. package/docs/api/scripts/collapse.js +39 -0
  118. package/docs/api/scripts/commonNav.js +28 -0
  119. package/docs/api/scripts/linenumber.js +25 -0
  120. package/docs/api/scripts/nav.js +12 -0
  121. package/docs/api/scripts/polyfill.js +4 -0
  122. package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -0
  123. package/docs/api/scripts/prettify/lang-css.js +2 -0
  124. package/docs/api/scripts/prettify/prettify.js +28 -0
  125. package/docs/api/scripts/search.js +99 -0
  126. package/docs/api/state-manager.js.html +423 -0
  127. package/docs/api/styles/jsdoc.css +776 -0
  128. package/docs/api/styles/prettify.css +80 -0
  129. package/docs/examples.md +328 -0
  130. package/docs/getting-started.md +302 -0
  131. package/docs/installation.md +361 -0
  132. package/docs/templates.md +418 -0
  133. package/docs/validation-checklist.md +266 -0
  134. package/package.json +39 -12
  135. package/src/commands/cost.js +181 -0
  136. package/src/commands/create-story.js +283 -0
  137. package/src/commands/detect.js +104 -0
  138. package/src/commands/doctor.js +67 -0
  139. package/src/commands/generate.js +149 -0
  140. package/src/commands/init.js +69 -45
  141. package/src/commands/shard-spec.js +224 -0
  142. package/src/commands/sprint-status.js +250 -0
  143. package/src/commands/state.js +333 -0
  144. package/src/commands/sync.js +167 -0
  145. package/src/commands/update-pricing.js +206 -0
  146. package/src/commands/update.js +88 -13
  147. package/src/lib/complexity-analyzer.js +292 -0
  148. package/src/lib/cost-calculator.js +429 -0
  149. package/src/lib/design-system-generator.js +298 -0
  150. package/src/lib/state-manager.js +340 -0
  151. package/src/utils/file-copier.js +59 -0
  152. package/src/utils/version-checker.js +175 -0
@@ -0,0 +1,423 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>state-manager.js - MORPH-SPEC API Documentation</title>
7
+
8
+ <meta name="description" content="API documentation for MORPH-SPEC Framework libraries" />
9
+
10
+ <meta name="keywords" content="morph-spec, api, documentation, blazor, dotnet, framework" />
11
+ <meta name="keyword" content="morph-spec, api, documentation, blazor, dotnet, framework" />
12
+
13
+
14
+
15
+ <script src="scripts/prettify/prettify.js"></script>
16
+ <script src="scripts/prettify/lang-css.js"></script>
17
+ <!--[if lt IE 9]>
18
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
19
+ <![endif]-->
20
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
21
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
22
+ <script src="scripts/nav.js" defer></script>
23
+
24
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
25
+ </head>
26
+ <body>
27
+
28
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
29
+ <label for="nav-trigger" class="navicon-button x">
30
+ <div class="navicon"></div>
31
+ </label>
32
+
33
+ <label for="nav-trigger" class="overlay"></label>
34
+
35
+ <nav >
36
+
37
+ <input type="text" id="nav-search" placeholder="Search" />
38
+
39
+
40
+ <h2><a href="index.html">Home</a></h2><h2><a href="https://github.com/lucasPolymorphism/morph-spec-framework" target="_blank" class="menu-item" id="repository" >GitHub</a></h2><h2><a href="https://www.npmjs.com/package/@polymorphism-tech/morph-spec" target="_blank" class="menu-item" id="npm" >npm</a></h2><h3>Global</h3><ul><li><a href="global.html#AZURE_PRICING">AZURE_PRICING</a></li><li><a href="global.html#addAgent">addAgent</a></li><li><a href="global.html#addCheckpoint">addCheckpoint</a></li><li><a href="global.html#calculateBicepCost">calculateBicepCost</a></li><li><a href="global.html#calculateResourceCost">calculateResourceCost</a></li><li><a href="global.html#calculateTotalCosts">calculateTotalCosts</a></li><li><a href="global.html#ensureFeature">ensureFeature</a></li><li><a href="global.html#generateCSS">generateCSS</a></li><li><a href="global.html#generateDesignSystem">generateDesignSystem</a></li><li><a href="global.html#generateFluentTheme">generateFluentTheme</a></li><li><a href="global.html#generateMudTheme">generateMudTheme</a></li><li><a href="global.html#getFeature">getFeature</a></li><li><a href="global.html#getStatePath">getStatePath</a></li><li><a href="global.html#getSummary">getSummary</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#listFeatures">listFeatures</a></li><li><a href="global.html#loadAzurePricing">loadAzurePricing</a></li><li><a href="global.html#loadCostConfig">loadCostConfig</a></li><li><a href="global.html#loadState">loadState</a></li><li><a href="global.html#markOutput">markOutput</a></li><li><a href="global.html#parseBicepFile">parseBicepFile</a></li><li><a href="global.html#parseBicepFiles">parseBicepFiles</a></li><li><a href="global.html#parseColors">parseColors</a></li><li><a href="global.html#parseDesignSystem">parseDesignSystem</a></li><li><a href="global.html#parseSpacing">parseSpacing</a></li><li><a href="global.html#parseTypography">parseTypography</a></li><li><a href="global.html#removeAgent">removeAgent</a></li><li><a href="global.html#saveState">saveState</a></li><li><a href="global.html#stateExists">stateExists</a></li><li><a href="global.html#updateFeature">updateFeature</a></li><li><a href="global.html#updateFeatureMultiple">updateFeatureMultiple</a></li></ul>
41
+
42
+ </nav>
43
+
44
+ <div id="main">
45
+
46
+ <h1 class="page-title">state-manager.js</h1>
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+ <section>
55
+ <article>
56
+ <pre class="prettyprint source linenums"><code>/**
57
+ * MORPH-SPEC State Manager Library
58
+ *
59
+ * Manages state.json for tracking features, progress, agents, and checkpoints.
60
+ * Used both by CLI commands and internal automation.
61
+ */
62
+
63
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
64
+ import { join, dirname } from 'path';
65
+
66
+ const STATE_FILE_NAME = '.morph/state.json';
67
+
68
+ // ============================================================================
69
+ // Core Functions
70
+ // ============================================================================
71
+
72
+ /**
73
+ * Get the state file path (looks in current working directory)
74
+ */
75
+ export function getStatePath() {
76
+ return join(process.cwd(), STATE_FILE_NAME);
77
+ }
78
+
79
+ /**
80
+ * Check if state file exists
81
+ */
82
+ export function stateExists() {
83
+ return existsSync(getStatePath());
84
+ }
85
+
86
+ /**
87
+ * Load state from disk
88
+ * @param {boolean} throwOnError - If false, returns null instead of throwing
89
+ * @returns {Object|null} State object or null
90
+ */
91
+ export function loadState(throwOnError = true) {
92
+ const statePath = getStatePath();
93
+
94
+ if (!existsSync(statePath)) {
95
+ if (throwOnError) {
96
+ throw new Error(`State file not found: ${statePath}\nRun 'morph-spec state init' first.`);
97
+ }
98
+ return null;
99
+ }
100
+
101
+ try {
102
+ const content = readFileSync(statePath, 'utf8');
103
+ return JSON.parse(content);
104
+ } catch (err) {
105
+ if (throwOnError) {
106
+ throw new Error(`Failed to parse state.json: ${err.message}`);
107
+ }
108
+ return null;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Save state to disk
114
+ * @param {Object} state - State object to save
115
+ */
116
+ export function saveState(state) {
117
+ state.metadata = state.metadata || {};
118
+ state.metadata.lastUpdated = new Date().toISOString();
119
+
120
+ const statePath = getStatePath();
121
+ const stateDir = dirname(statePath);
122
+
123
+ // Ensure .morph directory exists
124
+ if (!existsSync(stateDir)) {
125
+ mkdirSync(stateDir, { recursive: true });
126
+ }
127
+
128
+ writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
129
+ }
130
+
131
+ /**
132
+ * Initialize new state file
133
+ * @param {Object} options - Options
134
+ * @param {boolean} options.force - Overwrite existing file
135
+ * @param {string} options.projectName - Project name
136
+ * @param {string} options.projectType - Project type (e.g., 'blazor-server')
137
+ * @returns {Object} Initial state
138
+ */
139
+ export function initState(options = {}) {
140
+ const { force = false, projectName = '{PROJECT_NAME}', projectType = 'blazor-server' } = options;
141
+
142
+ if (stateExists() &amp;&amp; !force) {
143
+ throw new Error('State file already exists. Use force=true to overwrite.');
144
+ }
145
+
146
+ const initialState = {
147
+ version: "2.1.1",
148
+ project: {
149
+ name: projectName,
150
+ type: projectType,
151
+ createdAt: new Date().toISOString(),
152
+ updatedAt: new Date().toISOString()
153
+ },
154
+ features: {},
155
+ metadata: {
156
+ totalFeatures: 0,
157
+ completedFeatures: 0,
158
+ totalCostEstimated: 0,
159
+ totalTimeSpent: 0,
160
+ lastUpdated: new Date().toISOString()
161
+ }
162
+ };
163
+
164
+ saveState(initialState);
165
+ return initialState;
166
+ }
167
+
168
+ // ============================================================================
169
+ // Feature Operations
170
+ // ============================================================================
171
+
172
+ /**
173
+ * Get feature from state
174
+ * @param {string} featureName - Feature name
175
+ * @returns {Object|null} Feature object or null
176
+ */
177
+ export function getFeature(featureName) {
178
+ const state = loadState();
179
+ return state.features[featureName] || null;
180
+ }
181
+
182
+ /**
183
+ * Create or get feature with default structure
184
+ * @param {string} featureName - Feature name
185
+ * @returns {Object} Feature object
186
+ */
187
+ function ensureFeature(featureName) {
188
+ const state = loadState();
189
+
190
+ if (!state.features[featureName]) {
191
+ state.features[featureName] = {
192
+ status: "draft",
193
+ phase: "proposal",
194
+ createdAt: new Date().toISOString(),
195
+ updatedAt: new Date().toISOString(),
196
+ activeAgents: [],
197
+ outputs: {
198
+ proposal: { created: false, path: `.morph/project/outputs/${featureName}/proposal.md` },
199
+ spec: { created: false, path: `.morph/project/outputs/${featureName}/spec.md` },
200
+ contracts: { created: false, path: `.morph/project/outputs/${featureName}/contracts.cs` },
201
+ tasks: { created: false, path: `.morph/project/outputs/${featureName}/tasks.json` },
202
+ uiDesignSystem: { created: false, path: `.morph/project/outputs/${featureName}/ui-design-system.md` },
203
+ uiMockups: { created: false, path: `.morph/project/outputs/${featureName}/ui-mockups.md` },
204
+ uiComponents: { created: false, path: `.morph/project/outputs/${featureName}/ui-components.md` },
205
+ uiFlows: { created: false, path: `.morph/project/outputs/${featureName}/ui-flows.md` },
206
+ decisions: { created: false, path: `.morph/project/outputs/${featureName}/decisions.md` },
207
+ recap: { created: false, path: `.morph/project/outputs/${featureName}/recap.md` }
208
+ },
209
+ tasks: {
210
+ total: 0,
211
+ completed: 0,
212
+ inProgress: 0,
213
+ pending: 0
214
+ },
215
+ checkpoints: [],
216
+ costs: {
217
+ estimated: 0,
218
+ approved: false,
219
+ approvedBy: null,
220
+ approvedAt: null
221
+ }
222
+ };
223
+
224
+ state.metadata.totalFeatures++;
225
+ saveState(state);
226
+ }
227
+
228
+ return state.features[featureName];
229
+ }
230
+
231
+ /**
232
+ * Update feature property (supports nested keys like "tasks.completed")
233
+ * @param {string} featureName - Feature name
234
+ * @param {string} key - Property key (supports dot notation)
235
+ * @param {any} value - Value to set
236
+ */
237
+ export function updateFeature(featureName, key, value) {
238
+ ensureFeature(featureName);
239
+ const state = loadState(); // Load AFTER ensuring feature exists
240
+
241
+ const keys = key.split('.');
242
+ let target = state.features[featureName];
243
+
244
+ for (let i = 0; i &lt; keys.length - 1; i++) {
245
+ if (!target[keys[i]]) {
246
+ target[keys[i]] = {};
247
+ }
248
+ target = target[keys[i]];
249
+ }
250
+
251
+ const finalKey = keys[keys.length - 1];
252
+ target[finalKey] = value;
253
+ state.features[featureName].updatedAt = new Date().toISOString();
254
+
255
+ saveState(state);
256
+ }
257
+
258
+ /**
259
+ * Update multiple feature properties at once
260
+ * @param {string} featureName - Feature name
261
+ * @param {Object} updates - Object with key-value pairs to update
262
+ */
263
+ export function updateFeatureMultiple(featureName, updates) {
264
+ ensureFeature(featureName);
265
+ const state = loadState(); // Load AFTER ensuring feature exists
266
+
267
+ for (const [key, value] of Object.entries(updates)) {
268
+ const keys = key.split('.');
269
+ let target = state.features[featureName];
270
+
271
+ for (let i = 0; i &lt; keys.length - 1; i++) {
272
+ if (!target[keys[i]]) {
273
+ target[keys[i]] = {};
274
+ }
275
+ target = target[keys[i]];
276
+ }
277
+
278
+ const finalKey = keys[keys.length - 1];
279
+ target[finalKey] = value;
280
+ }
281
+
282
+ state.features[featureName].updatedAt = new Date().toISOString();
283
+ saveState(state);
284
+ }
285
+
286
+ /**
287
+ * Add checkpoint to feature
288
+ * @param {string} featureName - Feature name
289
+ * @param {string} note - Checkpoint note
290
+ * @returns {Object} Checkpoint object
291
+ */
292
+ export function addCheckpoint(featureName, note) {
293
+ ensureFeature(featureName);
294
+ const state = loadState();
295
+ const feature = state.features[featureName];
296
+
297
+ const checkpoint = {
298
+ timestamp: new Date().toISOString(),
299
+ phase: feature.phase,
300
+ completedTasks: feature.tasks.completed,
301
+ note: note
302
+ };
303
+
304
+ feature.checkpoints.push(checkpoint);
305
+ feature.updatedAt = new Date().toISOString();
306
+
307
+ saveState(state);
308
+ return checkpoint;
309
+ }
310
+
311
+ /**
312
+ * Add agent to feature
313
+ * @param {string} featureName - Feature name
314
+ * @param {string} agentId - Agent ID
315
+ * @returns {boolean} True if added, false if already exists
316
+ */
317
+ export function addAgent(featureName, agentId) {
318
+ ensureFeature(featureName);
319
+ const state = loadState(); // Load AFTER ensuring feature exists
320
+
321
+ if (!state.features[featureName].activeAgents.includes(agentId)) {
322
+ state.features[featureName].activeAgents.push(agentId);
323
+ state.features[featureName].updatedAt = new Date().toISOString();
324
+ saveState(state);
325
+ return true;
326
+ }
327
+
328
+ return false;
329
+ }
330
+
331
+ /**
332
+ * Remove agent from feature
333
+ * @param {string} featureName - Feature name
334
+ * @param {string} agentId - Agent ID
335
+ * @returns {boolean} True if removed, false if not found
336
+ */
337
+ export function removeAgent(featureName, agentId) {
338
+ const state = loadState();
339
+
340
+ if (!state.features[featureName]) {
341
+ throw new Error(`Feature '${featureName}' not found.`);
342
+ }
343
+
344
+ const index = state.features[featureName].activeAgents.indexOf(agentId);
345
+ if (index > -1) {
346
+ state.features[featureName].activeAgents.splice(index, 1);
347
+ state.features[featureName].updatedAt = new Date().toISOString();
348
+ saveState(state);
349
+ return true;
350
+ }
351
+
352
+ return false;
353
+ }
354
+
355
+ /**
356
+ * Mark output as created
357
+ * @param {string} featureName - Feature name
358
+ * @param {string} outputType - Output type (proposal, spec, contracts, etc.)
359
+ */
360
+ export function markOutput(featureName, outputType) {
361
+ ensureFeature(featureName);
362
+ const state = loadState();
363
+
364
+ if (!state.features[featureName].outputs[outputType]) {
365
+ throw new Error(`Output type '${outputType}' not valid. Valid types: proposal, spec, contracts, tasks, uiDesignSystem, uiMockups, uiComponents, uiFlows, decisions, recap`);
366
+ }
367
+
368
+ state.features[featureName].outputs[outputType].created = true;
369
+ state.features[featureName].updatedAt = new Date().toISOString();
370
+
371
+ saveState(state);
372
+ }
373
+
374
+ /**
375
+ * List all features
376
+ * @returns {Array} Array of [featureName, featureObject] tuples
377
+ */
378
+ export function listFeatures() {
379
+ const state = loadState();
380
+ return Object.entries(state.features);
381
+ }
382
+
383
+ /**
384
+ * Get project summary
385
+ * @returns {Object} Summary with metadata
386
+ */
387
+ export function getSummary() {
388
+ const state = loadState();
389
+ return {
390
+ project: state.project,
391
+ metadata: state.metadata,
392
+ featuresCount: Object.keys(state.features).length
393
+ };
394
+ }
395
+ </code></pre>
396
+ </article>
397
+ </section>
398
+
399
+
400
+
401
+
402
+
403
+
404
+ </div>
405
+
406
+ <br class="clear">
407
+
408
+ <footer>
409
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
410
+ </footer>
411
+
412
+ <script>prettyPrint();</script>
413
+ <script src="scripts/polyfill.js"></script>
414
+ <script src="scripts/linenumber.js"></script>
415
+
416
+ <script src="scripts/search.js" defer></script>
417
+
418
+
419
+ <script src="scripts/collapse.js" defer></script>
420
+
421
+
422
+ </body>
423
+ </html>