scene-capability-engine 3.3.5 → 3.3.10
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.
- package/CHANGELOG.md +130 -78
- package/README.md +6 -6
- package/README.zh.md +6 -6
- package/bin/scene-capability-engine.js +129 -7
- package/docs/331-poc-adaptation-roadmap.md +3 -3
- package/docs/331-poc-dual-track-integration-guide.md +8 -8
- package/docs/331-poc-weekly-delivery-checklist.md +6 -6
- package/docs/README.md +4 -0
- package/docs/adopt-migration-guide.md +13 -13
- package/docs/adoption-guide.md +28 -28
- package/docs/agent-hooks-analysis.md +10 -10
- package/docs/architecture.md +13 -13
- package/docs/articles/ai-driven-development-philosophy-and-practice.en.md +3 -3
- package/docs/articles/ai-driven-development-philosophy-and-practice.md +3 -3
- package/docs/autonomous-control-guide.md +35 -35
- package/docs/command-reference.md +192 -153
- package/docs/cross-tool-guide.md +7 -7
- package/docs/developer-guide.md +8 -8
- package/docs/document-governance.md +15 -15
- package/docs/environment-management-guide.md +6 -6
- package/docs/examples/add-export-command/design.md +1 -1
- package/docs/faq.md +13 -13
- package/docs/handoff-profile-integration-guide.md +3 -3
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.json +7 -7
- package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.md +1 -1
- package/docs/integration-modes.md +12 -12
- package/docs/integration-philosophy.md +11 -11
- package/docs/interactive-customization/331-poc-sce-integration-checklist.md +24 -24
- package/docs/interactive-customization/README.md +43 -43
- package/docs/interactive-customization/business-mode-policy-baseline.json +33 -0
- package/docs/interactive-customization/dual-ui-mode-integration-guide.md +1 -1
- package/docs/interactive-customization/moqui-adapter-interface.md +2 -2
- package/docs/interactive-customization/moqui-copilot-integration-guide.md +1 -1
- package/docs/interactive-customization/moqui-interactive-template-playbook.md +4 -4
- package/docs/interactive-customization/phase-acceptance-evidence.md +2 -2
- package/docs/knowledge-management-guide.md +6 -6
- package/docs/manual-workflows-guide.md +4 -4
- package/docs/moqui-capability-matrix.md +3 -3
- package/docs/moqui-standard-rebuild-guide.md +8 -8
- package/docs/moqui-template-core-library-playbook.md +27 -27
- package/docs/multi-agent-coordination-guide.md +19 -19
- package/docs/multi-repo-management-guide.md +17 -17
- package/docs/quick-start-with-ai-tools.md +7 -7
- package/docs/quick-start.md +2 -2
- package/docs/release-checklist.md +4 -4
- package/docs/sce-business-mode-map.md +103 -0
- package/docs/security-governance-default-baseline.md +12 -12
- package/docs/spec-collaboration-guide.md +3 -3
- package/docs/spec-locking-guide.md +2 -2
- package/docs/spec-workflow.md +3 -3
- package/docs/starter-kit/README.md +4 -4
- package/docs/starter-kit/handoff-manifest.starter.json +2 -2
- package/docs/starter-kit/release.workflow.sample.yml +1 -1
- package/docs/steering-strategy-guide.md +15 -15
- package/docs/team-collaboration-guide.md +69 -69
- package/docs/testing-strategy.md +2 -2
- package/docs/tools/claude-guide.md +14 -4
- package/docs/tools/cursor-guide.md +14 -14
- package/docs/tools/generic-guide.md +9 -9
- package/docs/tools/kiro-guide.md +4 -4
- package/docs/tools/vscode-guide.md +13 -13
- package/docs/tools/windsurf-guide.md +6 -6
- package/docs/troubleshooting.md +22 -22
- package/docs/upgrade-guide.md +8 -8
- package/docs/value-observability-guide.md +3 -3
- package/docs/zh/README.md +6 -0
- package/docs/zh/quick-start.md +15 -15
- package/docs/zh/release-checklist.md +3 -3
- package/docs/zh/tools/claude-guide.md +16 -6
- package/docs/zh/tools/cursor-guide.md +11 -11
- package/docs/zh/tools/generic-guide.md +13 -13
- package/docs/zh/tools/kiro-guide.md +2 -2
- package/docs/zh/tools/vscode-guide.md +11 -11
- package/docs/zh/tools/windsurf-guide.md +11 -11
- package/docs/zh/value-observability-guide.md +3 -3
- package/lib/adoption/adoption-logger.js +1 -1
- package/lib/adoption/adoption-strategy.js +28 -28
- package/lib/adoption/backup-manager.js +3 -3
- package/lib/adoption/conflict-resolver.js +2 -2
- package/lib/adoption/detection-engine.js +8 -8
- package/lib/adoption/error-formatter.js +4 -4
- package/lib/adoption/file-classifier.js +6 -6
- package/lib/adoption/progress-reporter.js +1 -1
- package/lib/adoption/smart-orchestrator.js +10 -10
- package/lib/adoption/strategy-selector.js +6 -6
- package/lib/adoption/summary-generator.js +1 -1
- package/lib/adoption/template-sync.js +8 -8
- package/lib/auto/autonomous-engine.js +7 -7
- package/lib/auto/checkpoint-manager.js +1 -1
- package/lib/auto/close-loop-runner.js +12 -12
- package/lib/auto/error-recovery-manager.js +1 -1
- package/lib/auto/goal-decomposer.js +1 -1
- package/lib/auto/moqui-recovery-sequence.js +2 -2
- package/lib/auto/progress-tracker.js +1 -1
- package/lib/auto/state-manager.js +1 -1
- package/lib/backup/backup-system.js +10 -10
- package/lib/backup/selective-backup.js +4 -4
- package/lib/collab/agent-registry.js +2 -2
- package/lib/collab/contract-manager.js +1 -1
- package/lib/collab/coordinator.js +2 -2
- package/lib/collab/dependency-manager.js +1 -1
- package/lib/collab/integration-manager.js +1 -1
- package/lib/collab/metadata-manager.js +1 -1
- package/lib/collab/multi-agent-config.js +2 -2
- package/lib/collab/spec-lifecycle-manager.js +2 -2
- package/lib/collab/visualizer.js +1 -1
- package/lib/commands/adopt.js +6 -6
- package/lib/commands/auto.js +56 -56
- package/lib/commands/collab.js +2 -2
- package/lib/commands/docs.js +3 -3
- package/lib/commands/doctor.js +1 -1
- package/lib/commands/knowledge.js +2 -2
- package/lib/commands/lock.js +1 -1
- package/lib/commands/ops.js +1 -1
- package/lib/commands/orchestrate.js +3 -3
- package/lib/commands/rollback.js +1 -1
- package/lib/commands/scene.js +135 -93
- package/lib/commands/session.js +139 -0
- package/lib/commands/spec-bootstrap.js +1 -1
- package/lib/commands/spec-gate.js +2 -2
- package/lib/commands/spec-pipeline.js +1 -1
- package/lib/commands/status.js +4 -4
- package/lib/commands/steering.js +119 -0
- package/lib/commands/value.js +1 -1
- package/lib/commands/watch.js +9 -9
- package/lib/commands/workspace-multi.js +1 -1
- package/lib/context/context-exporter.js +5 -7
- package/lib/context/prompt-generator.js +2 -2
- package/lib/environment/backup-system.js +1 -1
- package/lib/environment/environment-manager.js +2 -2
- package/lib/gitignore/gitignore-backup.js +3 -3
- package/lib/gitignore/gitignore-detector.js +13 -13
- package/lib/gitignore/gitignore-integration.js +3 -3
- package/lib/gitignore/gitignore-transformer.js +4 -4
- package/lib/gitignore/layered-rules-template.js +16 -16
- package/lib/governance/config-manager.js +1 -1
- package/lib/governance/doc-reference-checker.js +4 -4
- package/lib/governance/execution-logger.js +1 -1
- package/lib/governance/file-scanner.js +3 -3
- package/lib/interactive-customization/moqui-interactive-adapter.js +2 -2
- package/lib/knowledge/knowledge-manager.js +1 -1
- package/lib/lock/lock-manager.js +2 -2
- package/lib/lock/steering-file-lock.js +5 -5
- package/lib/lock/task-lock-manager.js +3 -3
- package/lib/operations/audit-logger.js +1 -1
- package/lib/operations/feedback-manager.js +1 -1
- package/lib/operations/operations-manager.js +3 -3
- package/lib/operations/permission-manager.js +2 -2
- package/lib/operations/template-loader.js +1 -1
- package/lib/orchestrator/agent-spawner.js +27 -2
- package/lib/orchestrator/bootstrap-prompt-builder.js +6 -6
- package/lib/orchestrator/orchestration-engine.js +1 -1
- package/lib/orchestrator/orchestrator-config.js +2 -2
- package/lib/repo/config-manager.js +3 -3
- package/lib/repo/handlers/init-handler.js +1 -1
- package/lib/repo/repo-manager.js +2 -2
- package/lib/runtime/business-mode-resolver.js +240 -0
- package/lib/runtime/session-store.js +207 -0
- package/lib/runtime/steering-contract.js +338 -0
- package/lib/scene-runtime/audit-emitter.js +1 -1
- package/lib/scene-runtime/binding-plugin-loader.js +3 -3
- package/lib/scene-runtime/eval-bridge.js +1 -1
- package/lib/scene-runtime/index.js +1 -1
- package/lib/scene-runtime/moqui-extractor.js +1 -1
- package/lib/scene-runtime/plan-compiler.js +1 -1
- package/lib/scene-runtime/policy-gate.js +1 -1
- package/lib/scene-runtime/runtime-executor.js +1 -1
- package/lib/scene-runtime/scene-loader.js +1 -1
- package/lib/spec/bootstrap/context-collector.js +1 -1
- package/lib/spec/pipeline/stage-adapters.js +3 -3
- package/lib/spec/pipeline/state-store.js +1 -1
- package/lib/spec-gate/policy/policy-loader.js +1 -1
- package/lib/spec-gate/rules/default-rules.js +6 -6
- package/lib/steering/adoption-config.js +1 -1
- package/lib/steering/compliance-error-reporter.js +3 -3
- package/lib/steering/context-sync-manager.js +2 -2
- package/lib/steering/index.js +1 -1
- package/lib/steering/spec-steering.js +2 -2
- package/lib/steering/steering-compliance-checker.js +1 -1
- package/lib/steering/steering-loader.js +4 -5
- package/lib/steering/steering-manager.js +4 -4
- package/lib/task/task-claimer.js +5 -5
- package/lib/task/task-status-store.js +2 -2
- package/lib/templates/content-generalizer.js +1 -1
- package/lib/templates/spec-reader.js +2 -2
- package/lib/templates/template-creator.js +1 -1
- package/lib/templates/template-exporter.js +3 -3
- package/lib/templates/template-manager.js +1 -1
- package/lib/upgrade/migration-engine.js +3 -3
- package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +1 -1
- package/lib/utils/file-diff.js +6 -6
- package/lib/utils/tool-detector.js +10 -10
- package/lib/utils/validation.js +5 -5
- package/lib/value/metric-contract-loader.js +1 -1
- package/lib/version/version-manager.js +1 -1
- package/lib/watch/execution-logger.js +1 -1
- package/lib/watch/presets.js +8 -8
- package/lib/watch/watch-manager.js +2 -2
- package/lib/workspace/legacy-kiro-migrator.js +275 -0
- package/lib/workspace/multi/workspace-context-resolver.js +2 -2
- package/lib/workspace/multi/workspace-registry.js +2 -2
- package/lib/workspace/multi/workspace-state-manager.js +3 -3
- package/lib/workspace/workspace-manager.js +1 -1
- package/lib/workspace/workspace-sync.js +2 -2
- package/locales/en.json +4 -4
- package/locales/zh.json +4 -4
- package/package.json +9 -9
- package/template/{.kiro → .sce}/README.md +15 -15
- package/template/{.kiro → .sce}/hooks/check-spec-on-create.kiro.hook +2 -2
- package/template/{.kiro → .sce}/steering/CORE_PRINCIPLES.md +4 -4
- package/template/{.kiro → .sce}/steering/CURRENT_CONTEXT.md +1 -1
- package/template/{.kiro → .sce}/steering/ENVIRONMENT.md +3 -3
- package/template/{.kiro → .sce}/tools/backup_manager.py +3 -3
- package/template/{.kiro → .sce}/tools/configuration_manager.py +1 -1
- package/template/README.md +12 -12
- /package/template/{.kiro → .sce}/hooks/run-tests-on-save.kiro.hook +0 -0
- /package/template/{.kiro → .sce}/hooks/sync-tasks-on-edit.kiro.hook +0 -0
- /package/template/{.kiro → .sce}/specs/SPEC_WORKFLOW_GUIDE.md +0 -0
- /package/template/{.kiro → .sce}/steering/RULES_GUIDE.md +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/change-impact.md +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/deployment.md +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/feedback-response.md +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/migration-plan.md +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/monitoring.md +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/operations.md +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/rollback.md +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/tools.yaml +0 -0
- /package/template/{.kiro → .sce}/templates/operations/default/troubleshooting.md +0 -0
- /package/template/{.kiro → .sce}/tools/document_evaluator.py +0 -0
- /package/template/{.kiro → .sce}/tools/enhancement_logger.py +0 -0
- /package/template/{.kiro → .sce}/tools/error_handler.py +0 -0
- /package/template/{.kiro → .sce}/tools/improvement_identifier.py +0 -0
- /package/template/{.kiro → .sce}/tools/modification_applicator.py +0 -0
- /package/template/{.kiro → .sce}/tools/quality_gate_enforcer.py +0 -0
- /package/template/{.kiro → .sce}/tools/quality_scorer.py +0 -0
- /package/template/{.kiro → .sce}/tools/report_generator.py +0 -0
- /package/template/{.kiro → .sce}/tools/ultrawork_enhancer.py +0 -0
- /package/template/{.kiro → .sce}/tools/ultrawork_enhancer_refactored.py +0 -0
- /package/template/{.kiro → .sce}/tools/ultrawork_enhancer_v2.py +0 -0
- /package/template/{.kiro → .sce}/tools/ultrawork_enhancer_v3.py +0 -0
- /package/template/{.kiro → .sce}/tools/workflow_quality_gate.py +0 -0
|
@@ -9,8 +9,8 @@ const inquirer = require('inquirer');
|
|
|
9
9
|
*/
|
|
10
10
|
class SteeringManager {
|
|
11
11
|
constructor() {
|
|
12
|
-
this.steeringDir = '.
|
|
13
|
-
this.backupBaseDir = '.
|
|
12
|
+
this.steeringDir = '.sce/steering';
|
|
13
|
+
this.backupBaseDir = '.sce/backups';
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -83,7 +83,7 @@ class SteeringManager {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
console.log('');
|
|
86
|
-
console.log('AI IDE loads all files in .
|
|
86
|
+
console.log('AI IDE loads all files in .sce/steering/, which means you must');
|
|
87
87
|
console.log('choose between sce steering rules OR your project\'s existing rules.');
|
|
88
88
|
console.log('');
|
|
89
89
|
|
|
@@ -169,7 +169,7 @@ class SteeringManager {
|
|
|
169
169
|
*/
|
|
170
170
|
async installKseSteering(projectPath) {
|
|
171
171
|
const steeringPath = path.join(projectPath, this.steeringDir);
|
|
172
|
-
const templatePath = path.join(__dirname, '../../template/.
|
|
172
|
+
const templatePath = path.join(__dirname, '../../template/.sce/steering');
|
|
173
173
|
|
|
174
174
|
try {
|
|
175
175
|
// 确保 steering 目录存在
|
package/lib/task/task-claimer.js
CHANGED
|
@@ -202,7 +202,7 @@ class TaskClaimer {
|
|
|
202
202
|
* @returns {Promise<Object>} 认领结果
|
|
203
203
|
*/
|
|
204
204
|
async claimTask(projectPath, specName, taskId, username, force = false) {
|
|
205
|
-
const tasksPath = path.join(projectPath, '.
|
|
205
|
+
const tasksPath = path.join(projectPath, '.sce/specs', specName, 'tasks.md');
|
|
206
206
|
|
|
207
207
|
try {
|
|
208
208
|
// 检查文件是否存在
|
|
@@ -284,7 +284,7 @@ class TaskClaimer {
|
|
|
284
284
|
* @returns {Promise<Object>} 释放结果
|
|
285
285
|
*/
|
|
286
286
|
async unclaimTask(projectPath, specName, taskId, username) {
|
|
287
|
-
const tasksPath = path.join(projectPath, '.
|
|
287
|
+
const tasksPath = path.join(projectPath, '.sce/specs', specName, 'tasks.md');
|
|
288
288
|
|
|
289
289
|
try {
|
|
290
290
|
// 检查文件是否存在
|
|
@@ -360,7 +360,7 @@ class TaskClaimer {
|
|
|
360
360
|
* @returns {Promise<Array>} 已认领的任务列表
|
|
361
361
|
*/
|
|
362
362
|
async getClaimedTasks(projectPath, specName) {
|
|
363
|
-
const tasksPath = path.join(projectPath, '.
|
|
363
|
+
const tasksPath = path.join(projectPath, '.sce/specs', specName, 'tasks.md');
|
|
364
364
|
|
|
365
365
|
try {
|
|
366
366
|
const exists = await fs.pathExists(tasksPath);
|
|
@@ -397,7 +397,7 @@ class TaskClaimer {
|
|
|
397
397
|
* @returns {Promise<Object>} 更新结果
|
|
398
398
|
*/
|
|
399
399
|
async updateTaskStatus(projectPath, specName, taskId, status) {
|
|
400
|
-
const tasksPath = path.join(projectPath, '.
|
|
400
|
+
const tasksPath = path.join(projectPath, '.sce/specs', specName, 'tasks.md');
|
|
401
401
|
|
|
402
402
|
try {
|
|
403
403
|
const exists = await fs.pathExists(tasksPath);
|
|
@@ -461,7 +461,7 @@ class TaskClaimer {
|
|
|
461
461
|
* @returns {Promise<Array>} 所有已认领的任务
|
|
462
462
|
*/
|
|
463
463
|
async getAllClaimedTasks(projectPath) {
|
|
464
|
-
const specsPath = path.join(projectPath, '.
|
|
464
|
+
const specsPath = path.join(projectPath, '.sce/specs');
|
|
465
465
|
|
|
466
466
|
try {
|
|
467
467
|
const exists = await fs.pathExists(specsPath);
|
|
@@ -233,7 +233,7 @@ class TaskStatusStore {
|
|
|
233
233
|
* @returns {string}
|
|
234
234
|
*/
|
|
235
235
|
_tasksPath(specName) {
|
|
236
|
-
return path.join(this._workspaceRoot, '.
|
|
236
|
+
return path.join(this._workspaceRoot, '.sce/specs', specName, 'tasks.md');
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
/**
|
|
@@ -242,7 +242,7 @@ class TaskStatusStore {
|
|
|
242
242
|
* @returns {string}
|
|
243
243
|
*/
|
|
244
244
|
_lockPath(specName) {
|
|
245
|
-
return path.join(this._workspaceRoot, '.
|
|
245
|
+
return path.join(this._workspaceRoot, '.sce/specs', specName, 'tasks.md.lock');
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// ---------------------------------------------------------------------------
|
|
@@ -152,7 +152,7 @@ class ContentGeneralizer {
|
|
|
152
152
|
if (specMetadata.specPath) {
|
|
153
153
|
replacements.push({
|
|
154
154
|
pattern: new RegExp(this.escapeRegex(specMetadata.specPath), 'g'),
|
|
155
|
-
variable: '.
|
|
155
|
+
variable: '.sce/specs/{{SPEC_NAME}}',
|
|
156
156
|
priority: 5,
|
|
157
157
|
description: 'Spec path'
|
|
158
158
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SpecReader - Reads and validates Spec files from .
|
|
2
|
+
* SpecReader - Reads and validates Spec files from .sce/specs/ directory
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const fs = require('fs').promises;
|
|
@@ -8,7 +8,7 @@ const { execSync } = require('child_process');
|
|
|
8
8
|
|
|
9
9
|
class SpecReader {
|
|
10
10
|
constructor() {
|
|
11
|
-
this.specsDir = '.
|
|
11
|
+
this.specsDir = '.sce/specs';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -180,7 +180,7 @@ class TemplateCreator {
|
|
|
180
180
|
|
|
181
181
|
// Check for remaining project-specific content (high confidence patterns)
|
|
182
182
|
const projectSpecificPatterns = [
|
|
183
|
-
/\.
|
|
183
|
+
/\.sce\/specs\/\d+-\d+-[a-z-]+/g, // Specific spec paths
|
|
184
184
|
/scene-capability-engine/g, // Project name (unless it's the template itself)
|
|
185
185
|
];
|
|
186
186
|
|
|
@@ -7,7 +7,7 @@ const path = require('path');
|
|
|
7
7
|
|
|
8
8
|
class TemplateExporter {
|
|
9
9
|
constructor() {
|
|
10
|
-
this.defaultOutputDir = '.
|
|
10
|
+
this.defaultOutputDir = '.sce/templates/exports';
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -177,7 +177,7 @@ Test your template locally before submitting:
|
|
|
177
177
|
sce spec create test-spec --template ${metadata.name}
|
|
178
178
|
|
|
179
179
|
# Verify the generated Spec is correct
|
|
180
|
-
cd .
|
|
180
|
+
cd .sce/specs/test-spec
|
|
181
181
|
# Check that variables were replaced correctly
|
|
182
182
|
\`\`\`
|
|
183
183
|
|
|
@@ -404,7 +404,7 @@ sce templates show ${metadata.category}/${metadata.name}
|
|
|
404
404
|
sce spec create my-new-feature --template ${metadata.category}/${metadata.name}
|
|
405
405
|
\`\`\`
|
|
406
406
|
|
|
407
|
-
This will create a new Spec at \`.
|
|
407
|
+
This will create a new Spec at \`.sce/specs/XX-00-my-new-feature/\` with:
|
|
408
408
|
- \`requirements.md\` - Requirements document
|
|
409
409
|
- \`design.md\` - Design document
|
|
410
410
|
- \`tasks.md\` - Implementation tasks
|
|
@@ -716,7 +716,7 @@ class TemplateManager {
|
|
|
716
716
|
}
|
|
717
717
|
|
|
718
718
|
// Determine target directory
|
|
719
|
-
const targetDir = path.join(process.cwd(), '.
|
|
719
|
+
const targetDir = path.join(process.cwd(), '.sce', 'specs', specName);
|
|
720
720
|
|
|
721
721
|
// Apply template
|
|
722
722
|
const result = await this.applicator.applyTemplate(
|
|
@@ -299,12 +299,12 @@ class MigrationEngine {
|
|
|
299
299
|
const warnings = [];
|
|
300
300
|
|
|
301
301
|
try {
|
|
302
|
-
// Check if .
|
|
303
|
-
const kiroPath = path.join(projectPath, '.
|
|
302
|
+
// Check if .sce/ directory exists
|
|
303
|
+
const kiroPath = path.join(projectPath, '.sce');
|
|
304
304
|
const kiroExists = await pathExists(kiroPath);
|
|
305
305
|
|
|
306
306
|
if (!kiroExists) {
|
|
307
|
-
errors.push('.
|
|
307
|
+
errors.push('.sce/ directory not found');
|
|
308
308
|
return { success: false, errors, warnings };
|
|
309
309
|
}
|
|
310
310
|
|
package/lib/utils/file-diff.js
CHANGED
|
@@ -157,12 +157,12 @@ class FileDiff {
|
|
|
157
157
|
*/
|
|
158
158
|
async compareSteeringFiles(projectRoot, templateRoot) {
|
|
159
159
|
const steeringFiles = [
|
|
160
|
-
'.
|
|
161
|
-
'.
|
|
162
|
-
'.
|
|
163
|
-
'.
|
|
164
|
-
'.
|
|
165
|
-
'.
|
|
160
|
+
'.sce/steering/CORE_PRINCIPLES.md',
|
|
161
|
+
'.sce/steering/ENVIRONMENT.md',
|
|
162
|
+
'.sce/steering/CURRENT_CONTEXT.md',
|
|
163
|
+
'.sce/steering/RULES_GUIDE.md',
|
|
164
|
+
'.sce/specs/SPEC_WORKFLOW_GUIDE.md',
|
|
165
|
+
'.sce/README.md'
|
|
166
166
|
];
|
|
167
167
|
|
|
168
168
|
const filePairs = steeringFiles.map(file => ({
|
|
@@ -63,19 +63,19 @@ async function detectKiroIDE(projectPath) {
|
|
|
63
63
|
let detected = false;
|
|
64
64
|
let confidence = 'low';
|
|
65
65
|
|
|
66
|
-
// Check for .
|
|
67
|
-
const kiroDir = path.join(projectPath, '.
|
|
66
|
+
// Check for .sce directory
|
|
67
|
+
const kiroDir = path.join(projectPath, '.sce');
|
|
68
68
|
if (await fs.pathExists(kiroDir)) {
|
|
69
|
-
indicators.push('.
|
|
69
|
+
indicators.push('.sce directory exists');
|
|
70
70
|
detected = true;
|
|
71
71
|
confidence = 'medium';
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// Check for SCE-specific files
|
|
75
75
|
const kiroFiles = [
|
|
76
|
-
'.
|
|
77
|
-
'.
|
|
78
|
-
'.
|
|
76
|
+
'.sce/steering',
|
|
77
|
+
'.sce/specs',
|
|
78
|
+
'.sce/tools'
|
|
79
79
|
];
|
|
80
80
|
|
|
81
81
|
for (const file of kiroFiles) {
|
|
@@ -87,7 +87,7 @@ async function detectKiroIDE(projectPath) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// Check for environment variables (if running in SCE)
|
|
90
|
-
if (process.env.
|
|
90
|
+
if (process.env.sce_IDE === 'true' || process.env.sce_VERSION) {
|
|
91
91
|
indicators.push('SCE environment variables detected');
|
|
92
92
|
detected = true;
|
|
93
93
|
confidence = 'high';
|
|
@@ -215,7 +215,7 @@ function getRecommendations(primaryTool, detections) {
|
|
|
215
215
|
type: 'native',
|
|
216
216
|
title: 'Use SCE Agent Hooks',
|
|
217
217
|
description: 'You can use native SCE agent hooks for seamless automation',
|
|
218
|
-
action: 'Configure hooks in .
|
|
218
|
+
action: 'Configure hooks in .sce/hooks/'
|
|
219
219
|
});
|
|
220
220
|
recommendations.push({
|
|
221
221
|
type: 'optional',
|
|
@@ -317,7 +317,7 @@ async function generateAutoConfig(detection, projectPath) {
|
|
|
317
317
|
case 'SCE':
|
|
318
318
|
config.suggestedPresets = [];
|
|
319
319
|
config.suggestedCommands = [
|
|
320
|
-
'Use native SCE agent hooks (see .
|
|
320
|
+
'Use native SCE agent hooks (see .sce/hooks/)',
|
|
321
321
|
'Optional: sce watch init (for watch mode fallback)'
|
|
322
322
|
];
|
|
323
323
|
config.notes.push('AI IDE detected - native hooks are recommended');
|
|
@@ -332,7 +332,7 @@ async function generateAutoConfig(detection, projectPath) {
|
|
|
332
332
|
'sce watch install auto-sync',
|
|
333
333
|
'sce watch start'
|
|
334
334
|
];
|
|
335
|
-
config.configPath = path.join(projectPath, '.
|
|
335
|
+
config.configPath = path.join(projectPath, '.sce/watch-config.json');
|
|
336
336
|
config.notes.push(`${primaryTool === 'vscode' ? 'VS Code' : 'Cursor'} detected - watch mode recommended`);
|
|
337
337
|
config.notes.push('Run suggested commands to set up automation');
|
|
338
338
|
break;
|
package/lib/utils/validation.js
CHANGED
|
@@ -21,12 +21,12 @@ async function validateProjectStructure(projectPath) {
|
|
|
21
21
|
const warnings = [];
|
|
22
22
|
|
|
23
23
|
try {
|
|
24
|
-
const kiroPath = path.join(projectPath, '.
|
|
24
|
+
const kiroPath = path.join(projectPath, '.sce');
|
|
25
25
|
|
|
26
|
-
// Check if .
|
|
26
|
+
// Check if .sce/ directory exists
|
|
27
27
|
const kiroExists = await pathExists(kiroPath);
|
|
28
28
|
if (!kiroExists) {
|
|
29
|
-
errors.push('.
|
|
29
|
+
errors.push('.sce/ directory not found');
|
|
30
30
|
return { success: false, errors, warnings };
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -110,7 +110,7 @@ async function validateVersionFile(projectPath) {
|
|
|
110
110
|
const warnings = [];
|
|
111
111
|
|
|
112
112
|
try {
|
|
113
|
-
const versionPath = path.join(projectPath, '.
|
|
113
|
+
const versionPath = path.join(projectPath, '.sce', 'version.json');
|
|
114
114
|
|
|
115
115
|
// Check if version.json exists
|
|
116
116
|
const exists = await pathExists(versionPath);
|
|
@@ -190,7 +190,7 @@ async function validateDependencies(projectPath) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// Check Python version (if Ultrawork tools are present)
|
|
193
|
-
const toolPath = path.join(projectPath, '.
|
|
193
|
+
const toolPath = path.join(projectPath, '.sce/tools/ultrawork_enhancer.py');
|
|
194
194
|
const toolExists = await pathExists(toolPath);
|
|
195
195
|
|
|
196
196
|
if (toolExists) {
|
|
@@ -32,7 +32,7 @@ class VersionManager {
|
|
|
32
32
|
* @returns {string} - Absolute path to version.json
|
|
33
33
|
*/
|
|
34
34
|
getVersionFilePath(projectPath) {
|
|
35
|
-
return path.join(projectPath, '.
|
|
35
|
+
return path.join(projectPath, '.sce', this.versionFileName);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -12,7 +12,7 @@ class ExecutionLogger extends EventEmitter {
|
|
|
12
12
|
super();
|
|
13
13
|
|
|
14
14
|
this.config = {
|
|
15
|
-
logDir: config.logDir || '.
|
|
15
|
+
logDir: config.logDir || '.sce/watch/logs',
|
|
16
16
|
logFile: config.logFile || 'execution.log',
|
|
17
17
|
maxLogSize: config.maxLogSize || 10 * 1024 * 1024, // 10MB
|
|
18
18
|
maxLogFiles: config.maxLogFiles || 5,
|
package/lib/watch/presets.js
CHANGED
|
@@ -33,17 +33,17 @@ const promptRegenPreset = {
|
|
|
33
33
|
name: 'prompt-regen',
|
|
34
34
|
description: 'Regenerate prompts when requirements or design changes',
|
|
35
35
|
patterns: [
|
|
36
|
-
'**/.
|
|
37
|
-
'**/.
|
|
36
|
+
'**/.sce/specs/*/requirements.md',
|
|
37
|
+
'**/.sce/specs/*/design.md'
|
|
38
38
|
],
|
|
39
39
|
actions: {
|
|
40
|
-
'**/.
|
|
40
|
+
'**/.sce/specs/*/requirements.md': {
|
|
41
41
|
command: 'sce prompt regenerate ${spec}',
|
|
42
42
|
debounce: 5000,
|
|
43
43
|
retry: true,
|
|
44
44
|
description: 'Regenerate prompts when requirements change'
|
|
45
45
|
},
|
|
46
|
-
'**/.
|
|
46
|
+
'**/.sce/specs/*/design.md': {
|
|
47
47
|
command: 'sce prompt regenerate ${spec}',
|
|
48
48
|
debounce: 5000,
|
|
49
49
|
retry: true,
|
|
@@ -53,8 +53,8 @@ const promptRegenPreset = {
|
|
|
53
53
|
debounce: {
|
|
54
54
|
default: 5000,
|
|
55
55
|
perPattern: {
|
|
56
|
-
'**/.
|
|
57
|
-
'**/.
|
|
56
|
+
'**/.sce/specs/*/requirements.md': 5000,
|
|
57
|
+
'**/.sce/specs/*/design.md': 5000
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
};
|
|
@@ -66,9 +66,9 @@ const promptRegenPreset = {
|
|
|
66
66
|
const contextExportPreset = {
|
|
67
67
|
name: 'context-export',
|
|
68
68
|
description: 'Export context when work is complete',
|
|
69
|
-
patterns: ['**/.
|
|
69
|
+
patterns: ['**/.sce/specs/*/.complete'],
|
|
70
70
|
actions: {
|
|
71
|
-
'**/.
|
|
71
|
+
'**/.sce/specs/*/.complete': {
|
|
72
72
|
command: 'sce context export ${spec}',
|
|
73
73
|
debounce: 1000,
|
|
74
74
|
retry: true,
|
|
@@ -16,7 +16,7 @@ class WatchManager extends EventEmitter {
|
|
|
16
16
|
super();
|
|
17
17
|
|
|
18
18
|
this.config = {
|
|
19
|
-
configFile: config.configFile || '.
|
|
19
|
+
configFile: config.configFile || '.sce/watch-config.json',
|
|
20
20
|
basePath: config.basePath || process.cwd(),
|
|
21
21
|
autoStart: config.autoStart !== false,
|
|
22
22
|
...config
|
|
@@ -150,7 +150,7 @@ class WatchManager extends EventEmitter {
|
|
|
150
150
|
async _initializeComponents() {
|
|
151
151
|
// 1. 初始化 logger
|
|
152
152
|
this.logger = new ExecutionLogger({
|
|
153
|
-
logDir: path.join(this.config.basePath, '.
|
|
153
|
+
logDir: path.join(this.config.basePath, '.sce/watch/logs'),
|
|
154
154
|
logLevel: this.watchConfig.logging?.level || 'info',
|
|
155
155
|
maxLogSize: this._parseSize(this.watchConfig.logging?.maxSize || '10MB'),
|
|
156
156
|
enableRotation: this.watchConfig.logging?.rotation !== false
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const LEGACY_DIRNAME = '.kiro';
|
|
5
|
+
const TARGET_DIRNAME = '.sce';
|
|
6
|
+
const DEFAULT_MAX_DEPTH = 6;
|
|
7
|
+
const DEFAULT_IGNORE_DIRS = new Set([
|
|
8
|
+
'.git',
|
|
9
|
+
'node_modules',
|
|
10
|
+
TARGET_DIRNAME,
|
|
11
|
+
'dist',
|
|
12
|
+
'build',
|
|
13
|
+
'coverage',
|
|
14
|
+
'.next',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
async function findLegacyKiroDirectories(rootDir, options = {}) {
|
|
18
|
+
const maxDepth = Number.isInteger(options.maxDepth) ? options.maxDepth : DEFAULT_MAX_DEPTH;
|
|
19
|
+
const ignoreDirs = options.ignoreDirs instanceof Set
|
|
20
|
+
? options.ignoreDirs
|
|
21
|
+
: DEFAULT_IGNORE_DIRS;
|
|
22
|
+
const results = [];
|
|
23
|
+
|
|
24
|
+
async function walk(currentDir, depth) {
|
|
25
|
+
if (depth > maxDepth) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let entries;
|
|
29
|
+
try {
|
|
30
|
+
entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
31
|
+
} catch (_error) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry.isDirectory()) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
40
|
+
if (entry.name === LEGACY_DIRNAME) {
|
|
41
|
+
results.push(fullPath);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (ignoreDirs.has(entry.name)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
await walk(fullPath, depth + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await walk(rootDir, 0);
|
|
52
|
+
results.sort((a, b) => a.length - b.length);
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function migrateLegacyKiroDirectories(rootDir, options = {}) {
|
|
57
|
+
const dryRun = options.dryRun === true;
|
|
58
|
+
const legacyDirs = await findLegacyKiroDirectories(rootDir, options);
|
|
59
|
+
const report = {
|
|
60
|
+
root: rootDir,
|
|
61
|
+
dryRun,
|
|
62
|
+
scanned: legacyDirs.length,
|
|
63
|
+
migrated: 0,
|
|
64
|
+
renamed: 0,
|
|
65
|
+
merged: 0,
|
|
66
|
+
moved_files: 0,
|
|
67
|
+
deduped_files: 0,
|
|
68
|
+
conflict_files: 0,
|
|
69
|
+
details: [],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Deepest first avoids parent directory interactions in nested projects.
|
|
73
|
+
const ordered = [...legacyDirs].sort((a, b) => b.length - a.length);
|
|
74
|
+
for (const legacyDir of ordered) {
|
|
75
|
+
const detail = await _migrateSingleLegacyDirectory(legacyDir, { dryRun });
|
|
76
|
+
report.details.push(detail);
|
|
77
|
+
if (!detail.success) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
report.migrated += 1;
|
|
81
|
+
report.renamed += detail.action === 'rename' ? 1 : 0;
|
|
82
|
+
report.merged += detail.action === 'merge' ? 1 : 0;
|
|
83
|
+
report.moved_files += detail.moved_files || 0;
|
|
84
|
+
report.deduped_files += detail.deduped_files || 0;
|
|
85
|
+
report.conflict_files += detail.conflict_files || 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return report;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function autoMigrateLegacyKiroDirectories(rootDir, options = {}) {
|
|
92
|
+
const dryRun = options.dryRun === true;
|
|
93
|
+
const report = await migrateLegacyKiroDirectories(rootDir, {
|
|
94
|
+
...options,
|
|
95
|
+
dryRun,
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
detected: report.scanned,
|
|
99
|
+
migrated: report.migrated,
|
|
100
|
+
report,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function _migrateSingleLegacyDirectory(legacyDir, options) {
|
|
105
|
+
const dryRun = options.dryRun === true;
|
|
106
|
+
const parentDir = path.dirname(legacyDir);
|
|
107
|
+
const targetDir = path.join(parentDir, TARGET_DIRNAME);
|
|
108
|
+
const detail = {
|
|
109
|
+
source: legacyDir,
|
|
110
|
+
target: targetDir,
|
|
111
|
+
success: true,
|
|
112
|
+
action: 'rename',
|
|
113
|
+
moved_files: 0,
|
|
114
|
+
deduped_files: 0,
|
|
115
|
+
conflict_files: 0,
|
|
116
|
+
errors: [],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const targetExists = await fs.pathExists(targetDir);
|
|
121
|
+
if (!targetExists) {
|
|
122
|
+
if (!dryRun) {
|
|
123
|
+
await fs.move(legacyDir, targetDir, { overwrite: false });
|
|
124
|
+
}
|
|
125
|
+
return detail;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
detail.action = 'merge';
|
|
129
|
+
const mergeReport = await _mergeLegacyIntoTarget(legacyDir, targetDir, { dryRun });
|
|
130
|
+
detail.moved_files = mergeReport.moved_files;
|
|
131
|
+
detail.deduped_files = mergeReport.deduped_files;
|
|
132
|
+
detail.conflict_files = mergeReport.conflict_files;
|
|
133
|
+
detail.errors.push(...mergeReport.errors);
|
|
134
|
+
detail.success = mergeReport.errors.length === 0;
|
|
135
|
+
return detail;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
detail.success = false;
|
|
138
|
+
detail.errors.push(error.message);
|
|
139
|
+
return detail;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function _mergeLegacyIntoTarget(legacyDir, targetDir, options) {
|
|
144
|
+
const dryRun = options.dryRun === true;
|
|
145
|
+
const report = {
|
|
146
|
+
moved_files: 0,
|
|
147
|
+
deduped_files: 0,
|
|
148
|
+
conflict_files: 0,
|
|
149
|
+
errors: [],
|
|
150
|
+
};
|
|
151
|
+
const files = await _listFilesRecursive(legacyDir);
|
|
152
|
+
|
|
153
|
+
for (const sourcePath of files) {
|
|
154
|
+
const relative = path.relative(legacyDir, sourcePath);
|
|
155
|
+
const targetPath = path.join(targetDir, relative);
|
|
156
|
+
try {
|
|
157
|
+
const targetExists = await fs.pathExists(targetPath);
|
|
158
|
+
if (!targetExists) {
|
|
159
|
+
report.moved_files += 1;
|
|
160
|
+
if (!dryRun) {
|
|
161
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
162
|
+
await fs.move(sourcePath, targetPath, { overwrite: false });
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const same = await _isSameFile(sourcePath, targetPath);
|
|
168
|
+
if (same) {
|
|
169
|
+
report.deduped_files += 1;
|
|
170
|
+
if (!dryRun) {
|
|
171
|
+
await fs.remove(sourcePath);
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const conflictPath = await _nextConflictPath(targetPath);
|
|
177
|
+
report.conflict_files += 1;
|
|
178
|
+
if (!dryRun) {
|
|
179
|
+
await fs.ensureDir(path.dirname(conflictPath));
|
|
180
|
+
await fs.move(sourcePath, conflictPath, { overwrite: false });
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
report.errors.push(`${sourcePath}: ${error.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!dryRun) {
|
|
188
|
+
await _removeEmptyDirectories(legacyDir);
|
|
189
|
+
if (await fs.pathExists(legacyDir)) {
|
|
190
|
+
const remaining = await fs.readdir(legacyDir);
|
|
191
|
+
if (remaining.length === 0) {
|
|
192
|
+
await fs.remove(legacyDir);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return report;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function _listFilesRecursive(rootDir) {
|
|
201
|
+
const files = [];
|
|
202
|
+
|
|
203
|
+
async function walk(currentDir) {
|
|
204
|
+
let entries = [];
|
|
205
|
+
try {
|
|
206
|
+
entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
207
|
+
} catch (_error) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
212
|
+
if (entry.isDirectory()) {
|
|
213
|
+
await walk(fullPath);
|
|
214
|
+
} else if (entry.isFile()) {
|
|
215
|
+
files.push(fullPath);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await walk(rootDir);
|
|
221
|
+
return files;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function _isSameFile(leftPath, rightPath) {
|
|
225
|
+
const [leftBuffer, rightBuffer] = await Promise.all([
|
|
226
|
+
fs.readFile(leftPath),
|
|
227
|
+
fs.readFile(rightPath),
|
|
228
|
+
]);
|
|
229
|
+
return leftBuffer.equals(rightBuffer);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function _nextConflictPath(targetPath) {
|
|
233
|
+
const { dir, name, ext } = path.parse(targetPath);
|
|
234
|
+
let counter = 1;
|
|
235
|
+
while (true) {
|
|
236
|
+
const candidate = path.join(dir, `${name}.legacy-kiro-${counter}${ext}`);
|
|
237
|
+
if (!await fs.pathExists(candidate)) {
|
|
238
|
+
return candidate;
|
|
239
|
+
}
|
|
240
|
+
counter += 1;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function _removeEmptyDirectories(rootDir) {
|
|
245
|
+
let entries = [];
|
|
246
|
+
try {
|
|
247
|
+
entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
248
|
+
} catch (_error) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
if (!entry.isDirectory()) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const fullPath = path.join(rootDir, entry.name);
|
|
256
|
+
await _removeEmptyDirectories(fullPath);
|
|
257
|
+
try {
|
|
258
|
+
const childEntries = await fs.readdir(fullPath);
|
|
259
|
+
if (childEntries.length === 0) {
|
|
260
|
+
await fs.rmdir(fullPath);
|
|
261
|
+
}
|
|
262
|
+
} catch (_error) {
|
|
263
|
+
// Ignore delete race/errors.
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = {
|
|
269
|
+
LEGACY_DIRNAME,
|
|
270
|
+
TARGET_DIRNAME,
|
|
271
|
+
findLegacyKiroDirectories,
|
|
272
|
+
migrateLegacyKiroDirectories,
|
|
273
|
+
autoMigrateLegacyKiroDirectories,
|
|
274
|
+
};
|
|
275
|
+
|