onto-mcp 0.3.2 → 0.4.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.
- package/.onto/processes/reconstruct/actionable-ontology-seed-recomposition-design.md +447 -0
- package/.onto/processes/reconstruct/foundry-style-ontology-seed-contract.md +934 -0
- package/.onto/processes/reconstruct/reconstruct-boundary-contract.md +303 -725
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +1645 -0
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +26 -22
- package/.onto/processes/reconstruct/source-profile-contract.md +49 -23
- package/.onto/processes/reconstruct/source-profiles/code.md +6 -3
- package/.onto/processes/reconstruct/source-profiles/database.md +5 -2
- package/.onto/processes/reconstruct/source-profiles/document.md +5 -2
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +5 -4
- package/.onto/processes/review/review-execution-ux-contract.md +40 -0
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +26 -10
- package/.onto/processes/shared/target-material-kind-contract.md +29 -16
- package/AGENTS.md +6 -4
- package/README.md +135 -76
- package/dist/cli.js +8 -8
- package/dist/core-api/reconstruct-api.js +117 -31
- package/dist/core-api/review-api.js +47 -0
- package/dist/core-runtime/cli/codex-review-unit-executor.js +39 -2
- package/dist/core-runtime/cli/complete-review-session.js +2 -2
- package/dist/core-runtime/cli/mock-review-unit-executor.js +1 -1
- package/dist/core-runtime/cli/review-invoke.js +9 -9
- package/dist/core-runtime/cli/run-review-prompt-execution.js +39 -5
- package/dist/core-runtime/cli/spawn-watcher.js +266 -47
- package/dist/core-runtime/cli/start-review-session.js +3 -3
- package/dist/core-runtime/llm/llm-caller.js +11 -0
- package/dist/core-runtime/llm/llm-tool-loop.js +2 -0
- package/dist/core-runtime/observability/runtime-stream-observation.js +118 -0
- package/dist/core-runtime/onboard/cli-host.js +149 -0
- package/dist/core-runtime/onboard/host-target.js +22 -0
- package/dist/core-runtime/onboard/json-config-host.js +122 -0
- package/dist/core-runtime/onboard/path-scan.js +26 -0
- package/dist/core-runtime/onboard/prompt.js +51 -0
- package/dist/core-runtime/onboard/register.js +207 -0
- package/dist/core-runtime/onboard/types.js +27 -0
- package/dist/core-runtime/reconstruct/actionable-seed-validation.js +1777 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +10 -4
- package/dist/core-runtime/reconstruct/contract-registry.js +623 -0
- package/dist/core-runtime/reconstruct/domain-id.js +10 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +716 -0
- package/dist/core-runtime/reconstruct/material-profile-validation.js +191 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +49 -11
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +269 -79
- package/dist/core-runtime/reconstruct/post-seed-validation.js +1194 -51
- package/dist/core-runtime/reconstruct/record.js +104 -20
- package/dist/core-runtime/reconstruct/run.js +2107 -413
- package/dist/core-runtime/reconstruct/seed-claim-projections.js +268 -0
- package/dist/core-runtime/reconstruct/source-profiles.js +93 -4
- package/dist/core-runtime/reconstruct/terminal-validation.js +807 -0
- package/dist/core-runtime/review/review-invocation-runner.js +4 -4
- package/dist/mcp/server.js +110 -38
- package/dist/mcp/tool-schemas.js +20 -6
- package/package.json +8 -17
- package/scripts/onto-review-watch.sh +486 -0
- package/scripts/onto-runtime-watch.sh +122 -0
- package/scripts/postinstall-hint.js +22 -0
- package/.onto/processes/reconstruct/top-level-concept-discovery-contract.md +0 -387
- package/dist/core-runtime/cli/bootstrap-review-binding.js +0 -186
- package/dist/core-runtime/cli/codex-nested-dispatch.test.js +0 -390
- package/dist/core-runtime/cli/codex-nested-teamlead-executor.test.js +0 -335
- package/dist/core-runtime/cli/coordinator-helpers.js +0 -583
- package/dist/core-runtime/cli/coordinator-state-machine-deliberation.test.js +0 -167
- package/dist/core-runtime/cli/coordinator-state-machine.js +0 -794
- package/dist/core-runtime/cli/e2e-codex-multi-agent-fixes.test.js +0 -615
- package/dist/core-runtime/cli/e2e-start-review-session.test.js +0 -312
- package/dist/core-runtime/cli/health.js +0 -44
- package/dist/core-runtime/cli/inline-http-review-unit-executor.test.js +0 -567
- package/dist/core-runtime/cli/materialize-review-execution-preparation.js +0 -104
- package/dist/core-runtime/cli/migrate-session-roots.js +0 -118
- package/dist/core-runtime/cli/repo-layout-migration-replace.smoke.test.js +0 -106
- package/dist/core-runtime/cli/review-invoke-auto-resolution.test.js +0 -268
- package/dist/core-runtime/cli/review-invoke-coordinator-topology.test.js +0 -136
- package/dist/core-runtime/cli/review-invoke-resolver-caching.test.js +0 -201
- package/dist/core-runtime/cli/review-invoke-topology-dispatch.test.js +0 -192
- package/dist/core-runtime/cli/session-root-guard.js +0 -168
- package/dist/core-runtime/cli/spawn-watcher.test.js +0 -457
- package/dist/core-runtime/cli/strip-wrapping-code-fence.test.js +0 -79
- package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.js +0 -412
- package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.test.js +0 -351
- package/dist/core-runtime/cli/topology-executor-mapping.js +0 -139
- package/dist/core-runtime/cli/topology-executor-mapping.test.js +0 -173
- package/dist/core-runtime/cli/write-review-interpretation.js +0 -81
- package/dist/core-runtime/config/onto-config-cli.js +0 -278
- package/dist/core-runtime/config/onto-config-key-path.js +0 -288
- package/dist/core-runtime/config/onto-config-key-path.test.js +0 -195
- package/dist/core-runtime/config/onto-config-preview.js +0 -108
- package/dist/core-runtime/config/onto-config-preview.test.js +0 -132
- package/dist/core-runtime/discovery/config-chain.js +0 -118
- package/dist/core-runtime/discovery/config-chain.test.js +0 -103
- package/dist/core-runtime/discovery/config-profile.js +0 -199
- package/dist/core-runtime/discovery/config-profile.test.js +0 -233
- package/dist/core-runtime/discovery/host-detection.test.js +0 -186
- package/dist/core-runtime/discovery/installation-paths.test.js +0 -65
- package/dist/core-runtime/discovery/lens-registry.test.js +0 -81
- package/dist/core-runtime/discovery/path-normalization.test.js +0 -22
- package/dist/core-runtime/discovery/plugin-path.js +0 -72
- package/dist/core-runtime/discovery/plugin-path.test.js +0 -95
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.js +0 -344
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.test.js +0 -915
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile.js +0 -564
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile.test.js +0 -708
- package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.js +0 -165
- package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.test.js +0 -227
- package/dist/core-runtime/evolve/adapters/code-product/validators/validate.js +0 -59
- package/dist/core-runtime/evolve/adapters/code-product/validators/validate.test.js +0 -205
- package/dist/core-runtime/evolve/adapters/methodology/adapter.js +0 -16
- package/dist/core-runtime/evolve/adapters/methodology/adapter.test.js +0 -9
- package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.js +0 -298
- package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.test.js +0 -70
- package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.js +0 -46
- package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.test.js +0 -73
- package/dist/core-runtime/evolve/adapters/registry.js +0 -47
- package/dist/core-runtime/evolve/adapters/registry.test.js +0 -67
- package/dist/core-runtime/evolve/cli.js +0 -256
- package/dist/core-runtime/evolve/commands/align.js +0 -194
- package/dist/core-runtime/evolve/commands/align.test.js +0 -82
- package/dist/core-runtime/evolve/commands/apply.js +0 -161
- package/dist/core-runtime/evolve/commands/apply.test.js +0 -138
- package/dist/core-runtime/evolve/commands/close.js +0 -39
- package/dist/core-runtime/evolve/commands/close.test.js +0 -99
- package/dist/core-runtime/evolve/commands/defer.js +0 -40
- package/dist/core-runtime/evolve/commands/defer.test.js +0 -134
- package/dist/core-runtime/evolve/commands/draft.js +0 -323
- package/dist/core-runtime/evolve/commands/draft.test.js +0 -178
- package/dist/core-runtime/evolve/commands/e2e-evolve-full-cycle.test.js +0 -208
- package/dist/core-runtime/evolve/commands/error-messages.js +0 -125
- package/dist/core-runtime/evolve/commands/error-messages.test.js +0 -167
- package/dist/core-runtime/evolve/commands/propose-align.js +0 -222
- package/dist/core-runtime/evolve/commands/propose-align.test.js +0 -136
- package/dist/core-runtime/evolve/commands/reconstruct.js +0 -330
- package/dist/core-runtime/evolve/commands/reconstruct.test.js +0 -278
- package/dist/core-runtime/evolve/commands/shared.js +0 -22
- package/dist/core-runtime/evolve/commands/stale-check.js +0 -103
- package/dist/core-runtime/evolve/commands/stale-check.test.js +0 -84
- package/dist/core-runtime/evolve/commands/start.js +0 -887
- package/dist/core-runtime/evolve/commands/start.test.js +0 -396
- package/dist/core-runtime/evolve/config/project-config.js +0 -99
- package/dist/core-runtime/evolve/config/project-config.test.js +0 -170
- package/dist/core-runtime/evolve/renderers/align-packet.js +0 -280
- package/dist/core-runtime/evolve/renderers/align-packet.test.js +0 -332
- package/dist/core-runtime/evolve/renderers/draft-packet.js +0 -303
- package/dist/core-runtime/evolve/renderers/draft-packet.test.js +0 -377
- package/dist/core-runtime/evolve/renderers/format.js +0 -5
- package/dist/core-runtime/evolve/renderers/scope-md.js +0 -237
- package/dist/core-runtime/evolve/renderers/scope-md.test.js +0 -306
- package/dist/core-runtime/govern/cli.js +0 -369
- package/dist/core-runtime/govern/cli.test.js +0 -314
- package/dist/core-runtime/govern/drift-engine.js +0 -103
- package/dist/core-runtime/govern/drift-engine.test.js +0 -319
- package/dist/core-runtime/govern/promote-principle.js +0 -206
- package/dist/core-runtime/govern/promote-principle.test.js +0 -368
- package/dist/core-runtime/govern/queue.js +0 -81
- package/dist/core-runtime/govern/types.js +0 -16
- package/dist/core-runtime/install/cli.js +0 -530
- package/dist/core-runtime/install/detect.js +0 -128
- package/dist/core-runtime/install/detect.test.js +0 -155
- package/dist/core-runtime/install/gitignore-update.js +0 -74
- package/dist/core-runtime/install/gitignore-update.test.js +0 -64
- package/dist/core-runtime/install/install-integration.test.js +0 -373
- package/dist/core-runtime/install/prompts.js +0 -389
- package/dist/core-runtime/install/prompts.test.js +0 -293
- package/dist/core-runtime/install/types.js +0 -26
- package/dist/core-runtime/install/validation.js +0 -295
- package/dist/core-runtime/install/validation.test.js +0 -313
- package/dist/core-runtime/install/writer.js +0 -254
- package/dist/core-runtime/install/writer.test.js +0 -218
- package/dist/core-runtime/learning/extractor.js +0 -461
- package/dist/core-runtime/learning/feedback.js +0 -179
- package/dist/core-runtime/learning/health-report.js +0 -165
- package/dist/core-runtime/learning/health-report.test.js +0 -169
- package/dist/core-runtime/learning/loader.js +0 -388
- package/dist/core-runtime/learning/loader.test.js +0 -102
- package/dist/core-runtime/learning/promote/apply-state.js +0 -240
- package/dist/core-runtime/learning/promote/audit-obligation.js +0 -195
- package/dist/core-runtime/learning/promote/collector.js +0 -432
- package/dist/core-runtime/learning/promote/degraded-state.js +0 -125
- package/dist/core-runtime/learning/promote/domain-doc-proposer.js +0 -166
- package/dist/core-runtime/learning/promote/e2e-promote.test.js +0 -6385
- package/dist/core-runtime/learning/promote/health-snapshot.js +0 -150
- package/dist/core-runtime/learning/promote/insight-reclassifier.js +0 -544
- package/dist/core-runtime/learning/promote/judgment-auditor.js +0 -517
- package/dist/core-runtime/learning/promote/panel-reviewer.js +0 -1158
- package/dist/core-runtime/learning/promote/promote-executor.js +0 -1675
- package/dist/core-runtime/learning/promote/promoter.js +0 -307
- package/dist/core-runtime/learning/promote/retirement.js +0 -122
- package/dist/core-runtime/learning/promote/types.js +0 -23
- package/dist/core-runtime/learning/prompt-sections.js +0 -51
- package/dist/core-runtime/learning/shared/artifact-registry-init.js +0 -45
- package/dist/core-runtime/learning/shared/artifact-registry.js +0 -254
- package/dist/core-runtime/learning/shared/audit-obligation-kernel.js +0 -73
- package/dist/core-runtime/learning/shared/audit-state.js +0 -99
- package/dist/core-runtime/learning/shared/duplicate-check.js +0 -28
- package/dist/core-runtime/learning/shared/llm-caller.js +0 -831
- package/dist/core-runtime/learning/shared/llm-caller.test.js +0 -601
- package/dist/core-runtime/learning/shared/llm-tool-loop.js +0 -393
- package/dist/core-runtime/learning/shared/mode.js +0 -25
- package/dist/core-runtime/learning/shared/paths.js +0 -84
- package/dist/core-runtime/learning/shared/paths.test.js +0 -79
- package/dist/core-runtime/learning/shared/patterns.js +0 -37
- package/dist/core-runtime/learning/shared/recoverability.js +0 -355
- package/dist/core-runtime/learning/shared/recovery-context.js +0 -374
- package/dist/core-runtime/learning/shared/scope.js +0 -1
- package/dist/core-runtime/learning/shared/semantic-classifier.js +0 -94
- package/dist/core-runtime/learning/shared/specs/apply-execution-state-spec.js +0 -42
- package/dist/core-runtime/learning/shared/specs/audit-state-spec.js +0 -37
- package/dist/core-runtime/learning/shared/specs/backup-metadata-spec.js +0 -39
- package/dist/core-runtime/learning/shared/specs/emergency-log-spec.js +0 -41
- package/dist/core-runtime/learning/shared/specs/layout-version-spec.js +0 -38
- package/dist/core-runtime/learning/shared/specs/promote-decisions-spec.js +0 -43
- package/dist/core-runtime/learning/shared/specs/promote-report-spec.js +0 -113
- package/dist/core-runtime/learning/shared/specs/prune-log-spec.js +0 -36
- package/dist/core-runtime/learning/shared/specs/recovery-resolution-spec.js +0 -48
- package/dist/core-runtime/learning/shared/specs/restore-manifest-spec.js +0 -43
- package/dist/core-runtime/learning/shared/specs/spec-helpers.js +0 -64
- package/dist/core-runtime/learning/usage-tracker.js +0 -190
- package/dist/core-runtime/learning/usage-tracker.test.js +0 -176
- package/dist/core-runtime/onboard/detect-review-axes.js +0 -122
- package/dist/core-runtime/onboard/detect-review-axes.test.js +0 -127
- package/dist/core-runtime/onboard/write-review-block.js +0 -188
- package/dist/core-runtime/onboard/write-review-block.test.js +0 -240
- package/dist/core-runtime/readers/brownfield-builder.js +0 -150
- package/dist/core-runtime/readers/brownfield-builder.test.js +0 -136
- package/dist/core-runtime/readers/code-chunk-collector.js +0 -53
- package/dist/core-runtime/readers/code-chunk-collector.test.js +0 -136
- package/dist/core-runtime/readers/file-utils.js +0 -240
- package/dist/core-runtime/readers/file-utils.test.js +0 -146
- package/dist/core-runtime/readers/lexicon-citation-check.js +0 -93
- package/dist/core-runtime/readers/lexicon-citation-check.test.js +0 -77
- package/dist/core-runtime/readers/mcp-figma.js +0 -30
- package/dist/core-runtime/readers/mcp-figma.test.js +0 -82
- package/dist/core-runtime/readers/mcp-generic.js +0 -31
- package/dist/core-runtime/readers/mcp-generic.test.js +0 -76
- package/dist/core-runtime/readers/ontology-index.js +0 -148
- package/dist/core-runtime/readers/ontology-index.test.js +0 -245
- package/dist/core-runtime/readers/ontology-query.js +0 -168
- package/dist/core-runtime/readers/ontology-query.test.js +0 -311
- package/dist/core-runtime/readers/ontology-resolve.js +0 -48
- package/dist/core-runtime/readers/ontology-resolve.test.js +0 -48
- package/dist/core-runtime/readers/patterns/index.js +0 -7
- package/dist/core-runtime/readers/review-log.js +0 -213
- package/dist/core-runtime/readers/review-log.test.js +0 -313
- package/dist/core-runtime/readers/scan-local.js +0 -102
- package/dist/core-runtime/readers/scan-local.test.js +0 -102
- package/dist/core-runtime/readers/scan-tarball.js +0 -121
- package/dist/core-runtime/readers/scan-tarball.test.js +0 -283
- package/dist/core-runtime/readers/scan-vault.js +0 -34
- package/dist/core-runtime/readers/scan-vault.test.js +0 -81
- package/dist/core-runtime/readers/types.js +0 -42
- package/dist/core-runtime/readers/types.test.js +0 -94
- package/dist/core-runtime/readers/viewpoint-collectors.js +0 -229
- package/dist/core-runtime/reconstruct/seed-candidate-validation.js +0 -385
- package/dist/core-runtime/review/citation-audit.test.js +0 -165
- package/dist/core-runtime/review/execution-plan-resolver.js +0 -247
- package/dist/core-runtime/review/execution-plan-resolver.test.js +0 -243
- package/dist/core-runtime/review/execution-topology-resolver-axis-first.test.js +0 -246
- package/dist/core-runtime/review/execution-topology-resolver.js +0 -401
- package/dist/core-runtime/review/execution-topology-resolver.test.js +0 -315
- package/dist/core-runtime/review/inline-context-embedder.test.js +0 -154
- package/dist/core-runtime/review/legacy-mode-policy.js +0 -88
- package/dist/core-runtime/review/materializers-effort-persist.test.js +0 -79
- package/dist/core-runtime/review/ontology-path-classifier.js +0 -179
- package/dist/core-runtime/review/ontology-path-classifier.test.js +0 -216
- package/dist/core-runtime/review/packet-boundary-policy.test.js +0 -107
- package/dist/core-runtime/review/participating-lens-paths.test.js +0 -73
- package/dist/core-runtime/review/review-config-legacy-translate.js +0 -244
- package/dist/core-runtime/review/review-config-legacy-translate.test.js +0 -161
- package/dist/core-runtime/review/review-config-validator.js +0 -289
- package/dist/core-runtime/review/review-config-validator.test.js +0 -236
- package/dist/core-runtime/review/shape-pipeline-audit.test.js +0 -311
- package/dist/core-runtime/review/shape-to-topology-id.js +0 -117
- package/dist/core-runtime/review/shape-to-topology-id.test.js +0 -132
- package/dist/core-runtime/review/topology-shape-derivation.js +0 -155
- package/dist/core-runtime/review/topology-shape-derivation.test.js +0 -195
- package/dist/core-runtime/scope-runtime/constants.js +0 -12
- package/dist/core-runtime/scope-runtime/constraint-pool.js +0 -166
- package/dist/core-runtime/scope-runtime/constraint-pool.test.js +0 -674
- package/dist/core-runtime/scope-runtime/domain-validation-log.js +0 -135
- package/dist/core-runtime/scope-runtime/domain-validation-log.test.js +0 -156
- package/dist/core-runtime/scope-runtime/eval-persistence.js +0 -65
- package/dist/core-runtime/scope-runtime/eval-persistence.test.js +0 -84
- package/dist/core-runtime/scope-runtime/event-pipeline.js +0 -64
- package/dist/core-runtime/scope-runtime/event-pipeline.test.js +0 -450
- package/dist/core-runtime/scope-runtime/event-store.js +0 -39
- package/dist/core-runtime/scope-runtime/event-store.test.js +0 -95
- package/dist/core-runtime/scope-runtime/gate-guard.js +0 -348
- package/dist/core-runtime/scope-runtime/gate-guard.test.js +0 -1047
- package/dist/core-runtime/scope-runtime/hash.js +0 -4
- package/dist/core-runtime/scope-runtime/hash.test.js +0 -33
- package/dist/core-runtime/scope-runtime/id.js +0 -4
- package/dist/core-runtime/scope-runtime/id.test.js +0 -17
- package/dist/core-runtime/scope-runtime/reducer.js +0 -297
- package/dist/core-runtime/scope-runtime/reducer.test.js +0 -759
- package/dist/core-runtime/scope-runtime/scope-manager.js +0 -161
- package/dist/core-runtime/scope-runtime/state-machine.js +0 -309
- package/dist/core-runtime/scope-runtime/state-machine.test.js +0 -704
- package/dist/core-runtime/scope-runtime/types.js +0 -116
- package/dist/core-runtime/scope-runtime/types.test.js +0 -69
- package/dist/core-runtime/translate/render-for-user.js +0 -169
- package/dist/core-runtime/translate/render-for-user.test.js +0 -122
- package/dist/providers/capability-contract.js +0 -1
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync, existsSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { executeStart, findExistingScope } from "./start.js";
|
|
6
|
-
import { readEvents } from "../../scope-runtime/event-store.js";
|
|
7
|
-
import { reduce } from "../../scope-runtime/reducer.js";
|
|
8
|
-
import { createScope } from "../../scope-runtime/scope-manager.js";
|
|
9
|
-
import { appendScopeEvent } from "../../scope-runtime/event-pipeline.js";
|
|
10
|
-
let projectDir;
|
|
11
|
-
let scopesDir;
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
projectDir = mkdtempSync(join(tmpdir(), "sprint-start-"));
|
|
14
|
-
scopesDir = join(projectDir, "scopes");
|
|
15
|
-
mkdirSync(scopesDir, { recursive: true });
|
|
16
|
-
});
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
19
|
-
});
|
|
20
|
-
function makeInput(overrides = {}) {
|
|
21
|
-
return {
|
|
22
|
-
rawInput: "튜터 차단 기능 추가",
|
|
23
|
-
projectRoot: projectDir,
|
|
24
|
-
scopesDir,
|
|
25
|
-
scopeId: "SC-TEST-001",
|
|
26
|
-
title: "튜터 차단 기능",
|
|
27
|
-
...overrides,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
describe("executeStart", () => {
|
|
31
|
-
it("creates scope and records events with local source", async () => {
|
|
32
|
-
// Create a local source directory
|
|
33
|
-
const srcDir = join(projectDir, "app-src");
|
|
34
|
-
mkdirSync(srcDir, { recursive: true });
|
|
35
|
-
writeFileSync(join(srcDir, "app.ts"), 'import { foo } from "./foo";');
|
|
36
|
-
writeFileSync(join(srcDir, "foo.ts"), "export const foo = 1;");
|
|
37
|
-
const result = await executeStart(makeInput({
|
|
38
|
-
rawInput: `튜터 차단 --add-dir ${srcDir}`,
|
|
39
|
-
}));
|
|
40
|
-
expect(result.success).toBe(true);
|
|
41
|
-
if (!result.success)
|
|
42
|
-
return;
|
|
43
|
-
// Verify scope was created
|
|
44
|
-
expect(result.paths.scopeId).toBe("SC-TEST-001");
|
|
45
|
-
// Verify scan results
|
|
46
|
-
const startResult = result;
|
|
47
|
-
expect(startResult.scanResults).toHaveLength(1);
|
|
48
|
-
expect(startResult.scanResults[0].files.length).toBeGreaterThanOrEqual(2);
|
|
49
|
-
expect(startResult.totalFiles).toBeGreaterThanOrEqual(2);
|
|
50
|
-
// Verify source_hashes uses {type}:{identifier} keys
|
|
51
|
-
const hashKeys = Object.keys(startResult.sourceHashes);
|
|
52
|
-
expect(hashKeys.length).toBe(1);
|
|
53
|
-
expect(hashKeys[0]).toMatch(/^add-dir:/);
|
|
54
|
-
// Verify events were recorded
|
|
55
|
-
const events = readEvents(result.paths.events);
|
|
56
|
-
const types = events.map(e => e.type);
|
|
57
|
-
expect(types).toContain("scope.created");
|
|
58
|
-
expect(types).toContain("grounding.started");
|
|
59
|
-
expect(types).toContain("grounding.completed");
|
|
60
|
-
// Verify state
|
|
61
|
-
const state = reduce(events);
|
|
62
|
-
expect(state.current_state).toBe("grounded");
|
|
63
|
-
expect(state.title).toBe("튜터 차단 기능");
|
|
64
|
-
expect(state.grounding_sources).toHaveLength(1);
|
|
65
|
-
// Verify reality-snapshot.json was written
|
|
66
|
-
const snapshot = JSON.parse(readFileSync(result.paths.realitySnapshot, "utf-8"));
|
|
67
|
-
expect(snapshot.scope_id).toBe("SC-TEST-001");
|
|
68
|
-
expect(snapshot.source_hashes).toEqual(startResult.sourceHashes);
|
|
69
|
-
// Verify sources.yaml was written
|
|
70
|
-
const sourcesContent = readFileSync(result.paths.sourcesYaml, "utf-8");
|
|
71
|
-
expect(sourcesContent).toContain("add-dir");
|
|
72
|
-
});
|
|
73
|
-
it("merges .sprint-kit.yaml defaults with /start flags", async () => {
|
|
74
|
-
// Create .sprint-kit.yaml with a default source
|
|
75
|
-
const defaultSrcDir = join(projectDir, "default-src");
|
|
76
|
-
mkdirSync(defaultSrcDir, { recursive: true });
|
|
77
|
-
writeFileSync(join(defaultSrcDir, "default.ts"), "const d = 1;");
|
|
78
|
-
writeFileSync(join(projectDir, ".sprint-kit.yaml"), `default_sources:\n - type: add-dir\n path: ${defaultSrcDir}\n description: default source\n`);
|
|
79
|
-
// Add another source via /start flag
|
|
80
|
-
const flagSrcDir = join(projectDir, "flag-src");
|
|
81
|
-
mkdirSync(flagSrcDir, { recursive: true });
|
|
82
|
-
writeFileSync(join(flagSrcDir, "flag.ts"), "const f = 1;");
|
|
83
|
-
const result = await executeStart(makeInput({
|
|
84
|
-
rawInput: `기능 추가 --add-dir ${flagSrcDir}`,
|
|
85
|
-
}));
|
|
86
|
-
expect(result.success).toBe(true);
|
|
87
|
-
if (!result.success)
|
|
88
|
-
return;
|
|
89
|
-
// Both sources scanned
|
|
90
|
-
const mergeResult = result;
|
|
91
|
-
expect(mergeResult.scanResults).toHaveLength(2);
|
|
92
|
-
expect(Object.keys(mergeResult.sourceHashes)).toHaveLength(2);
|
|
93
|
-
});
|
|
94
|
-
it("fails when no sources are specified", async () => {
|
|
95
|
-
const result = await executeStart(makeInput({
|
|
96
|
-
rawInput: "기능 추가",
|
|
97
|
-
}));
|
|
98
|
-
expect(result.success).toBe(false);
|
|
99
|
-
if (result.success)
|
|
100
|
-
return;
|
|
101
|
-
expect(result.step).toBe("resolve_sources");
|
|
102
|
-
expect(result.reason).toContain("소스가 없습니다");
|
|
103
|
-
});
|
|
104
|
-
it("reports scan errors without stopping", async () => {
|
|
105
|
-
// Create one valid source
|
|
106
|
-
const srcDir = join(projectDir, "valid-src");
|
|
107
|
-
mkdirSync(srcDir, { recursive: true });
|
|
108
|
-
writeFileSync(join(srcDir, "app.ts"), "const a = 1;");
|
|
109
|
-
const result = await executeStart(makeInput({
|
|
110
|
-
rawInput: `기능 추가 --add-dir ${srcDir} --add-dir /nonexistent/path`,
|
|
111
|
-
}));
|
|
112
|
-
expect(result.success).toBe(true);
|
|
113
|
-
if (!result.success)
|
|
114
|
-
return;
|
|
115
|
-
// One success, one empty (nonexistent path returns empty, not error)
|
|
116
|
-
expect(result.scanResults).toHaveLength(2);
|
|
117
|
-
});
|
|
118
|
-
it("calls onProgress callback", async () => {
|
|
119
|
-
const srcDir = join(projectDir, "src");
|
|
120
|
-
mkdirSync(srcDir, { recursive: true });
|
|
121
|
-
writeFileSync(join(srcDir, "a.ts"), "const a = 1;");
|
|
122
|
-
const messages = [];
|
|
123
|
-
const result = await executeStart(makeInput({
|
|
124
|
-
rawInput: `차단 --add-dir ${srcDir}`,
|
|
125
|
-
onProgress: (msg) => messages.push(msg),
|
|
126
|
-
}));
|
|
127
|
-
expect(result.success).toBe(true);
|
|
128
|
-
expect(messages.length).toBeGreaterThanOrEqual(2);
|
|
129
|
-
expect(messages[0]).toContain("소스 스캔을 시작합니다");
|
|
130
|
-
expect(messages[messages.length - 1]).toContain("소스 스캔 완료");
|
|
131
|
-
});
|
|
132
|
-
it("uses experience as default entry_mode", async () => {
|
|
133
|
-
const srcDir = join(projectDir, "src");
|
|
134
|
-
mkdirSync(srcDir, { recursive: true });
|
|
135
|
-
writeFileSync(join(srcDir, "a.ts"), "const a = 1;");
|
|
136
|
-
const result = await executeStart(makeInput({
|
|
137
|
-
rawInput: `차단 --add-dir ${srcDir}`,
|
|
138
|
-
}));
|
|
139
|
-
expect(result.success).toBe(true);
|
|
140
|
-
if (!result.success)
|
|
141
|
-
return;
|
|
142
|
-
const state = reduce(readEvents(result.paths.events));
|
|
143
|
-
expect(state.entry_mode).toBe("experience");
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
// ─── 3-path branching tests (TCOV-2) ───
|
|
147
|
-
describe("executeStart — Path A: New scope", () => {
|
|
148
|
-
it("returns StartInitResult with action: initialized, creates brief.md", async () => {
|
|
149
|
-
const result = await executeStart({
|
|
150
|
-
rawInput: "",
|
|
151
|
-
projectRoot: projectDir,
|
|
152
|
-
scopesDir,
|
|
153
|
-
projectName: "test-project",
|
|
154
|
-
});
|
|
155
|
-
expect(result.success).toBe(true);
|
|
156
|
-
expect(result).toHaveProperty("action", "initialized");
|
|
157
|
-
const initResult = result;
|
|
158
|
-
expect(initResult.scopeId).toMatch(/^test-project-\d{8}-\d{3}$/);
|
|
159
|
-
expect(initResult.briefPath).toBeDefined();
|
|
160
|
-
expect(existsSync(initResult.briefPath)).toBe(true);
|
|
161
|
-
const briefContent = readFileSync(initResult.briefPath, "utf-8");
|
|
162
|
-
expect(briefContent).toContain("변경 목적");
|
|
163
|
-
expect(briefContent).toContain("대상 사용자");
|
|
164
|
-
expect(briefContent).toContain("기대 결과");
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
describe("executeStart — Path B: Brief filled", () => {
|
|
168
|
-
it("reads brief, validates, creates scope.created event, starts grounding", async () => {
|
|
169
|
-
// Step 1: Create scope via Path A
|
|
170
|
-
const initResult = await executeStart({
|
|
171
|
-
rawInput: "",
|
|
172
|
-
projectRoot: projectDir,
|
|
173
|
-
scopesDir,
|
|
174
|
-
projectName: "brief-test",
|
|
175
|
-
});
|
|
176
|
-
// Step 2: Create a source directory
|
|
177
|
-
const srcDir = join(projectDir, "src-for-brief");
|
|
178
|
-
mkdirSync(srcDir, { recursive: true });
|
|
179
|
-
writeFileSync(join(srcDir, "app.ts"), "const x = 1;");
|
|
180
|
-
// Step 3: Fill in the brief with required fields + additional source
|
|
181
|
-
const filledBrief = `# brief-test — Brief
|
|
182
|
-
|
|
183
|
-
## 변경 목적 (필수)
|
|
184
|
-
사용자 차단 기능을 추가해야 합니다.
|
|
185
|
-
|
|
186
|
-
## 대상 사용자 (필수)
|
|
187
|
-
일반 사용자
|
|
188
|
-
|
|
189
|
-
## 기대 결과 (필수)
|
|
190
|
-
차단된 사용자가 매칭에서 제외됩니다.
|
|
191
|
-
|
|
192
|
-
## 포함 범위
|
|
193
|
-
|
|
194
|
-
## 제외 범위
|
|
195
|
-
|
|
196
|
-
## 제약 및 참고사항
|
|
197
|
-
|
|
198
|
-
## 소스
|
|
199
|
-
|
|
200
|
-
### 자동 로드 (환경설정)
|
|
201
|
-
- 환경설정 파일(.sprint-kit.yaml)이 없거나 소스가 정의되지 않았습니다.
|
|
202
|
-
|
|
203
|
-
### 추가 소스
|
|
204
|
-
- [x] 앱 소스 (add-dir: ${srcDir})
|
|
205
|
-
`;
|
|
206
|
-
writeFileSync(initResult.briefPath, filledBrief, "utf-8");
|
|
207
|
-
// Step 4: Run /start again with the same projectName
|
|
208
|
-
const result = await executeStart({
|
|
209
|
-
rawInput: "",
|
|
210
|
-
projectRoot: projectDir,
|
|
211
|
-
scopesDir,
|
|
212
|
-
projectName: "brief-test",
|
|
213
|
-
});
|
|
214
|
-
expect(result.success).toBe(true);
|
|
215
|
-
// Path B returns StartResult (not StartInitResult)
|
|
216
|
-
expect(result).not.toHaveProperty("action", "initialized");
|
|
217
|
-
expect(result).not.toHaveProperty("action", "resume_info");
|
|
218
|
-
if (!result.success)
|
|
219
|
-
return;
|
|
220
|
-
if ("action" in result && (result.action === "initialized" || result.action === "resume_info"))
|
|
221
|
-
return;
|
|
222
|
-
// Verify events were recorded
|
|
223
|
-
const events = readEvents(result.paths.events);
|
|
224
|
-
const types = events.map(e => e.type);
|
|
225
|
-
expect(types).toContain("scope.created");
|
|
226
|
-
expect(types).toContain("grounding.started");
|
|
227
|
-
expect(types).toContain("grounding.completed");
|
|
228
|
-
const state = reduce(events);
|
|
229
|
-
expect(state.current_state).toBe("grounded");
|
|
230
|
-
expect(state.title).toBe("사용자 차단 기능을 추가해야 합니다.");
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
describe("executeStart — Path B: Brief incomplete → exploration conversation mode", () => {
|
|
234
|
-
it("succeeds with briefValidation indicating missing fields", async () => {
|
|
235
|
-
// Step 1: Create scope via Path A
|
|
236
|
-
const initResult = await executeStart({
|
|
237
|
-
rawInput: "",
|
|
238
|
-
projectRoot: projectDir,
|
|
239
|
-
scopesDir,
|
|
240
|
-
projectName: "incomplete-brief",
|
|
241
|
-
});
|
|
242
|
-
// Step 2: Leave brief mostly empty (only fill purpose, leave others blank)
|
|
243
|
-
const incompleteBrief = `# incomplete-brief — Brief
|
|
244
|
-
|
|
245
|
-
## 변경 목적 (필수)
|
|
246
|
-
기능 추가
|
|
247
|
-
|
|
248
|
-
## 대상 사용자 (필수)
|
|
249
|
-
|
|
250
|
-
## 기대 결과 (필수)
|
|
251
|
-
|
|
252
|
-
## 소스
|
|
253
|
-
|
|
254
|
-
### 자동 로드 (환경설정)
|
|
255
|
-
- 없음
|
|
256
|
-
|
|
257
|
-
### 추가 소스
|
|
258
|
-
`;
|
|
259
|
-
writeFileSync(initResult.briefPath, incompleteBrief, "utf-8");
|
|
260
|
-
// Step 2.5: Create a local source and .sprint-kit.yaml so grounding can proceed
|
|
261
|
-
const srcDir = join(projectDir, "src-incomplete");
|
|
262
|
-
mkdirSync(srcDir, { recursive: true });
|
|
263
|
-
writeFileSync(join(srcDir, "index.ts"), "export const x = 1;");
|
|
264
|
-
writeFileSync(join(projectDir, ".sprint-kit.yaml"), `default_sources:\n - type: add-dir\n path: ${srcDir}\n description: test source\n`);
|
|
265
|
-
// Step 3: Run /start again — should succeed with briefValidation
|
|
266
|
-
const result = await executeStart({
|
|
267
|
-
rawInput: "",
|
|
268
|
-
projectRoot: projectDir,
|
|
269
|
-
scopesDir,
|
|
270
|
-
projectName: "incomplete-brief",
|
|
271
|
-
});
|
|
272
|
-
expect(result.success).toBe(true);
|
|
273
|
-
const success = result;
|
|
274
|
-
expect(success.briefValidation).toBeDefined();
|
|
275
|
-
expect(success.briefValidation.isComplete).toBe(false);
|
|
276
|
-
expect(success.briefValidation.missingFields).toContain("대상 사용자");
|
|
277
|
-
expect(success.briefValidation.missingFields).toContain("기대 결과");
|
|
278
|
-
expect(success.totalFiles).toBeGreaterThan(0);
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
describe("executeStart — Path C: Resume with events", () => {
|
|
282
|
-
it("returns StartResumeResult with current state info", async () => {
|
|
283
|
-
// Step 1: Create and execute a full start to get events
|
|
284
|
-
const srcDir = join(projectDir, "src-resume");
|
|
285
|
-
mkdirSync(srcDir, { recursive: true });
|
|
286
|
-
writeFileSync(join(srcDir, "a.ts"), "const a = 1;");
|
|
287
|
-
const scopeId = "resume-test-20260310-001";
|
|
288
|
-
const firstResult = await executeStart({
|
|
289
|
-
rawInput: `테스트 --add-dir ${srcDir}`,
|
|
290
|
-
projectRoot: projectDir,
|
|
291
|
-
scopesDir,
|
|
292
|
-
scopeId,
|
|
293
|
-
title: "Resume 테스트",
|
|
294
|
-
});
|
|
295
|
-
expect(firstResult.success).toBe(true);
|
|
296
|
-
// Step 2: Set up findExistingScope to find this scope
|
|
297
|
-
// Create .sprint-kit.yaml to not interfere
|
|
298
|
-
// Manually run executeStart with the same scopeId pattern
|
|
299
|
-
// Since findExistingScope uses normalized project name, we simulate by
|
|
300
|
-
// calling executeStart with the exact scopeId (backward compat path triggers on scopeId+title)
|
|
301
|
-
// Instead, let's use the new interface: create a matching directory name
|
|
302
|
-
// Read events to verify they exist
|
|
303
|
-
if (!firstResult.success)
|
|
304
|
-
return;
|
|
305
|
-
if ("action" in firstResult && firstResult.action === "initialized")
|
|
306
|
-
return;
|
|
307
|
-
const events = readEvents(firstResult.paths.events);
|
|
308
|
-
expect(events.length).toBeGreaterThan(0);
|
|
309
|
-
// Step 3: Now call again using the same scopeId+title (triggers backward compat path)
|
|
310
|
-
// For Path C testing, we need projectName-based lookup
|
|
311
|
-
// The existing scope dir is "resume-test-20260310-001", and findExistingScope
|
|
312
|
-
// searches for `{normalized}-{YYYYMMDD}-{NNN}` pattern.
|
|
313
|
-
// We can call findExistingScope directly to confirm, then trigger Path C via
|
|
314
|
-
// a new scope creation with the right name pattern.
|
|
315
|
-
// Actually, let's create a scope with known events using createScope + appendScopeEvent
|
|
316
|
-
const scopeId2 = "pathc-20260310-001";
|
|
317
|
-
mkdirSync(join(scopesDir, scopeId2), { recursive: true });
|
|
318
|
-
const paths2 = createScope(scopesDir, scopeId2);
|
|
319
|
-
appendScopeEvent(paths2, {
|
|
320
|
-
type: "scope.created",
|
|
321
|
-
actor: "user",
|
|
322
|
-
payload: { title: "Path C 테스트", description: "d", entry_mode: "experience" },
|
|
323
|
-
});
|
|
324
|
-
// Now executeStart with projectName "pathc" should find the existing scope
|
|
325
|
-
const resumeResult = await executeStart({
|
|
326
|
-
rawInput: "",
|
|
327
|
-
projectRoot: projectDir,
|
|
328
|
-
scopesDir,
|
|
329
|
-
projectName: "pathc",
|
|
330
|
-
});
|
|
331
|
-
expect(resumeResult.success).toBe(true);
|
|
332
|
-
expect(resumeResult).toHaveProperty("action", "resume_info");
|
|
333
|
-
const resume = resumeResult;
|
|
334
|
-
expect(resume.scopeId).toBe(scopeId2);
|
|
335
|
-
expect(resume.currentState).toBe("draft");
|
|
336
|
-
expect(resume.nextAction).toBeDefined();
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
describe("findExistingScope", () => {
|
|
340
|
-
it("returns most recent matching scope", () => {
|
|
341
|
-
// Create multiple matching directories
|
|
342
|
-
mkdirSync(join(scopesDir, "myapp-20260310-001"), { recursive: true });
|
|
343
|
-
mkdirSync(join(scopesDir, "myapp-20260310-002"), { recursive: true });
|
|
344
|
-
mkdirSync(join(scopesDir, "myapp-20260310-003"), { recursive: true });
|
|
345
|
-
const result = findExistingScope(scopesDir, "myapp");
|
|
346
|
-
expect(result).toBe("myapp-20260310-003");
|
|
347
|
-
});
|
|
348
|
-
it("returns null when no matching scope exists", () => {
|
|
349
|
-
const result = findExistingScope(scopesDir, "nonexistent");
|
|
350
|
-
expect(result).toBeNull();
|
|
351
|
-
});
|
|
352
|
-
it("returns null when scopesDir does not exist", () => {
|
|
353
|
-
const result = findExistingScope(join(projectDir, "no-such-dir"), "anything");
|
|
354
|
-
expect(result).toBeNull();
|
|
355
|
-
});
|
|
356
|
-
it("ignores non-matching directories", () => {
|
|
357
|
-
mkdirSync(join(scopesDir, "other-20260310-001"), { recursive: true });
|
|
358
|
-
mkdirSync(join(scopesDir, "target-20260310-001"), { recursive: true });
|
|
359
|
-
const result = findExistingScope(scopesDir, "target");
|
|
360
|
-
expect(result).toBe("target-20260310-001");
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
// ─── ScanSkipped integration test (ETag cache hit) ───
|
|
364
|
-
describe("ScanSkipped integration (ETag cache hit)", () => {
|
|
365
|
-
it("reuses cached hash when scanTarball returns ScanSkipped", async () => {
|
|
366
|
-
// Mock scanTarball to return ScanSkipped for github-tarball source
|
|
367
|
-
const { scanTarball } = await import("../../readers/scan-tarball.js");
|
|
368
|
-
const scanTarballSpy = vi.spyOn(await import("../../readers/scan-tarball.js"), "scanTarball");
|
|
369
|
-
scanTarballSpy.mockResolvedValue({
|
|
370
|
-
skipped: true,
|
|
371
|
-
source: { type: "github-tarball", url: "https://github.com/acme/repo" },
|
|
372
|
-
cached_hash: "cached-hash-abc",
|
|
373
|
-
});
|
|
374
|
-
// Also need a local source so grounding doesn't fail (needs at least 1 scanResult or scanSkipped)
|
|
375
|
-
const srcDir = join(projectDir, "local-src");
|
|
376
|
-
mkdirSync(srcDir, { recursive: true });
|
|
377
|
-
writeFileSync(join(srcDir, "app.ts"), "const a = 1;");
|
|
378
|
-
// Create .sprint-kit.yaml with github-tarball source
|
|
379
|
-
writeFileSync(join(projectDir, ".sprint-kit.yaml"), `default_sources:\n - type: github-tarball\n url: https://github.com/acme/repo\n description: test repo\n`);
|
|
380
|
-
const result = await executeStart(makeInput({
|
|
381
|
-
rawInput: `기능 추가 --add-dir ${srcDir}`,
|
|
382
|
-
}));
|
|
383
|
-
expect(result.success).toBe(true);
|
|
384
|
-
if (!result.success)
|
|
385
|
-
return;
|
|
386
|
-
// Verify cached hash is in sourceHashes
|
|
387
|
-
expect(result.sourceHashes["github-tarball:https://github.com/acme/repo"]).toBe("cached-hash-abc");
|
|
388
|
-
// Verify grounding.completed event contains the cached hash in source_hashes
|
|
389
|
-
const events = readEvents(result.paths.events);
|
|
390
|
-
const groundingCompleted = events.find(e => e.type === "grounding.completed");
|
|
391
|
-
expect(groundingCompleted).toBeDefined();
|
|
392
|
-
expect(groundingCompleted.payload.source_hashes["github-tarball:https://github.com/acme/repo"])
|
|
393
|
-
.toBe("cached-hash-abc");
|
|
394
|
-
scanTarballSpy.mockRestore();
|
|
395
|
-
});
|
|
396
|
-
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { parse, stringify } from "yaml";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import { getLogger } from "../../logger.js";
|
|
6
|
-
import { sourceKey } from "../../readers/types.js";
|
|
7
|
-
// ─── Zod Schemas ───
|
|
8
|
-
/**
|
|
9
|
-
* usage_hint: when this source should be loaded into agent context.
|
|
10
|
-
* - grounding_only: read once during grounding (default if omitted)
|
|
11
|
-
* - context: re-inject into agent context at surface generation and compile
|
|
12
|
-
* - full: always available (grounding + surface + compile + apply)
|
|
13
|
-
*/
|
|
14
|
-
const usageHintSchema = z.enum(["grounding_only", "context", "full"]).optional();
|
|
15
|
-
const sourceEntrySchema = z.discriminatedUnion("type", [
|
|
16
|
-
z.object({ type: z.literal("add-dir"), path: z.string(), description: z.string().optional(), usage_hint: usageHintSchema }),
|
|
17
|
-
z.object({ type: z.literal("github-tarball"), url: z.string(), description: z.string().optional(), usage_hint: usageHintSchema }),
|
|
18
|
-
z.object({ type: z.literal("figma-mcp"), file_key: z.string(), description: z.string().optional(), usage_hint: usageHintSchema }),
|
|
19
|
-
z.object({ type: z.literal("obsidian-vault"), path: z.string(), description: z.string().optional(), usage_hint: usageHintSchema }),
|
|
20
|
-
z.object({ type: z.literal("mcp"), provider: z.string(), description: z.string().optional(), usage_hint: usageHintSchema, tools: z.array(z.string()).optional(), query_policy: z.record(z.string(), z.unknown()).optional() }),
|
|
21
|
-
]);
|
|
22
|
-
export const projectConfigSchema = z.object({
|
|
23
|
-
default_sources: z.array(sourceEntrySchema),
|
|
24
|
-
target_stack: z.record(z.string(), z.string()).optional(),
|
|
25
|
-
apply_enabled: z.boolean().optional(),
|
|
26
|
-
});
|
|
27
|
-
const CONFIG_FILENAME = ".sprint-kit.yaml";
|
|
28
|
-
// ─── Load .sprint-kit.yaml ───
|
|
29
|
-
export function loadProjectConfig(projectRoot) {
|
|
30
|
-
const configPath = join(projectRoot, CONFIG_FILENAME);
|
|
31
|
-
if (!existsSync(configPath)) {
|
|
32
|
-
return { default_sources: [] };
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
36
|
-
const parsed = parse(raw);
|
|
37
|
-
if (!parsed || !Array.isArray(parsed.default_sources)) {
|
|
38
|
-
return { default_sources: [] };
|
|
39
|
-
}
|
|
40
|
-
const result = projectConfigSchema.safeParse(parsed);
|
|
41
|
-
if (!result.success) {
|
|
42
|
-
const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
43
|
-
getLogger().warn(`.sprint-kit.yaml 검증 실패. 기본값(빈 소스)으로 대체합니다.\n${issues}`);
|
|
44
|
-
return { default_sources: [] };
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
default_sources: result.data.default_sources,
|
|
48
|
-
target_stack: result.data.target_stack,
|
|
49
|
-
apply_enabled: result.data.apply_enabled,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
getLogger().debug("loadProjectConfig: failed to load config", { path: configPath, error });
|
|
54
|
-
return { default_sources: [] };
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// ─── Resolve Sources (merge defaults + additional, dedup) ───
|
|
58
|
-
export function resolveSources(defaults, additional) {
|
|
59
|
-
const seen = new Map();
|
|
60
|
-
for (const entry of defaults) {
|
|
61
|
-
seen.set(sourceKey(entry), entry);
|
|
62
|
-
}
|
|
63
|
-
for (const entry of additional) {
|
|
64
|
-
seen.set(sourceKey(entry), entry);
|
|
65
|
-
}
|
|
66
|
-
return Array.from(seen.values());
|
|
67
|
-
}
|
|
68
|
-
// ─── Write / Read sources.yaml ───
|
|
69
|
-
export function writeSourcesYaml(filePath, sources) {
|
|
70
|
-
const content = stringify({ sources });
|
|
71
|
-
writeFileSync(filePath, content, "utf-8");
|
|
72
|
-
}
|
|
73
|
-
export function readSourcesYaml(filePath) {
|
|
74
|
-
if (!existsSync(filePath)) {
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
78
|
-
const parsed = parse(raw);
|
|
79
|
-
if (!parsed || !Array.isArray(parsed.sources)) {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
return parsed.sources;
|
|
83
|
-
}
|
|
84
|
-
export function parseStartInput(text) {
|
|
85
|
-
const sources = [];
|
|
86
|
-
let description = text;
|
|
87
|
-
// --add-dir <path> (supports quoted paths)
|
|
88
|
-
description = description.replace(/--add-dir\s+(?:"([^"]+)"|(\S+))/g, (_, q, u) => { sources.push({ type: "add-dir", path: q ?? u }); return ""; });
|
|
89
|
-
// --github <url> (supports quoted urls)
|
|
90
|
-
description = description.replace(/--github\s+(?:"([^"]+)"|(\S+))/g, (_, q, u) => { sources.push({ type: "github-tarball", url: q ?? u }); return ""; });
|
|
91
|
-
// --figma <file_key> (supports quoted keys)
|
|
92
|
-
description = description.replace(/--figma\s+(?:"([^"]+)"|(\S+))/g, (_, q, u) => { sources.push({ type: "figma-mcp", file_key: q ?? u }); return ""; });
|
|
93
|
-
// --obsidian <path> (supports quoted paths)
|
|
94
|
-
description = description.replace(/--obsidian\s+(?:"([^"]+)"|(\S+))/g, (_, q, u) => { sources.push({ type: "obsidian-vault", path: q ?? u }); return ""; });
|
|
95
|
-
return {
|
|
96
|
-
description: description.replace(/\s+/g, " ").trim(),
|
|
97
|
-
sources,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { loadProjectConfig, resolveSources, writeSourcesYaml, readSourcesYaml, parseStartInput, } from "./project-config.js";
|
|
5
|
-
import { setLogger, silentLogger } from "../../logger.js";
|
|
6
|
-
const TMP = join(import.meta.dirname ?? ".", ".tmp-config-test");
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
mkdirSync(TMP, { recursive: true });
|
|
9
|
-
setLogger(silentLogger);
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => rmSync(TMP, { recursive: true, force: true }));
|
|
12
|
-
// ─── loadProjectConfig ───
|
|
13
|
-
describe("loadProjectConfig", () => {
|
|
14
|
-
it("returns empty when no config file", () => {
|
|
15
|
-
expect(loadProjectConfig(TMP).default_sources).toEqual([]);
|
|
16
|
-
});
|
|
17
|
-
it("loads sources from .sprint-kit.yaml", () => {
|
|
18
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), `default_sources:\n - type: add-dir\n path: ./src\n description: code\n`);
|
|
19
|
-
const config = loadProjectConfig(TMP);
|
|
20
|
-
expect(config.default_sources).toHaveLength(1);
|
|
21
|
-
expect(config.default_sources[0]).toEqual({ type: "add-dir", path: "./src", description: "code" });
|
|
22
|
-
});
|
|
23
|
-
it("returns empty for invalid yaml", () => {
|
|
24
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), "other_key: value\n");
|
|
25
|
-
expect(loadProjectConfig(TMP).default_sources).toEqual([]);
|
|
26
|
-
});
|
|
27
|
-
it("returns empty for empty file", () => {
|
|
28
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), "");
|
|
29
|
-
expect(loadProjectConfig(TMP).default_sources).toEqual([]);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
// ─── resolveSources ───
|
|
33
|
-
describe("resolveSources", () => {
|
|
34
|
-
it("merges defaults and additional", () => {
|
|
35
|
-
const d = [{ type: "add-dir", path: "./src" }];
|
|
36
|
-
const a = [{ type: "github-tarball", url: "https://github.com/org/repo" }];
|
|
37
|
-
expect(resolveSources(d, a)).toHaveLength(2);
|
|
38
|
-
});
|
|
39
|
-
it("deduplicates, additional wins", () => {
|
|
40
|
-
const d = [{ type: "add-dir", path: "./src", description: "old" }];
|
|
41
|
-
const a = [{ type: "add-dir", path: "./src", description: "new" }];
|
|
42
|
-
const result = resolveSources(d, a);
|
|
43
|
-
expect(result).toHaveLength(1);
|
|
44
|
-
expect(result[0].description).toBe("new");
|
|
45
|
-
});
|
|
46
|
-
it("handles empty inputs", () => {
|
|
47
|
-
expect(resolveSources([], [{ type: "add-dir", path: "./src" }])).toHaveLength(1);
|
|
48
|
-
expect(resolveSources([{ type: "add-dir", path: "./src" }], [])).toHaveLength(1);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
// ─── sources.yaml roundtrip ───
|
|
52
|
-
describe("sources.yaml", () => {
|
|
53
|
-
it("write then read preserves entries", () => {
|
|
54
|
-
const sources = [
|
|
55
|
-
{ type: "add-dir", path: "./src", description: "code" },
|
|
56
|
-
{ type: "figma-mcp", file_key: "xyz" },
|
|
57
|
-
];
|
|
58
|
-
const filePath = join(TMP, "sources.yaml");
|
|
59
|
-
writeSourcesYaml(filePath, sources);
|
|
60
|
-
expect(readSourcesYaml(filePath)).toEqual(sources);
|
|
61
|
-
});
|
|
62
|
-
it("returns empty for missing file", () => {
|
|
63
|
-
expect(readSourcesYaml(join(TMP, "nope.yaml"))).toEqual([]);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
// ─── parseStartInput ───
|
|
67
|
-
describe("parseStartInput", () => {
|
|
68
|
-
it("description only", () => {
|
|
69
|
-
const r = parseStartInput("튜터 차단 기능 추가");
|
|
70
|
-
expect(r.description).toBe("튜터 차단 기능 추가");
|
|
71
|
-
expect(r.sources).toEqual([]);
|
|
72
|
-
});
|
|
73
|
-
it("--add-dir", () => {
|
|
74
|
-
const r = parseStartInput("튜터 차단 --add-dir /projects/app/src");
|
|
75
|
-
expect(r.description).toBe("튜터 차단");
|
|
76
|
-
expect(r.sources).toEqual([{ type: "add-dir", path: "/projects/app/src" }]);
|
|
77
|
-
});
|
|
78
|
-
it("--github", () => {
|
|
79
|
-
const r = parseStartInput("기능 추가 --github https://github.com/org/repo");
|
|
80
|
-
expect(r.description).toBe("기능 추가");
|
|
81
|
-
expect(r.sources).toEqual([{ type: "github-tarball", url: "https://github.com/org/repo" }]);
|
|
82
|
-
});
|
|
83
|
-
it("--figma", () => {
|
|
84
|
-
const r = parseStartInput("화면 --figma abc123");
|
|
85
|
-
expect(r.description).toBe("화면");
|
|
86
|
-
expect(r.sources).toEqual([{ type: "figma-mcp", file_key: "abc123" }]);
|
|
87
|
-
});
|
|
88
|
-
it("--obsidian", () => {
|
|
89
|
-
const r = parseStartInput("정책 --obsidian /vaults/co");
|
|
90
|
-
expect(r.description).toBe("정책");
|
|
91
|
-
expect(r.sources).toEqual([{ type: "obsidian-vault", path: "/vaults/co" }]);
|
|
92
|
-
});
|
|
93
|
-
it("multiple flags", () => {
|
|
94
|
-
const r = parseStartInput("차단 --add-dir /app --figma xyz --github https://g.com/r");
|
|
95
|
-
expect(r.description).toBe("차단");
|
|
96
|
-
const types = r.sources.map(s => s.type).sort();
|
|
97
|
-
expect(types).toEqual(["add-dir", "figma-mcp", "github-tarball"]);
|
|
98
|
-
});
|
|
99
|
-
it("--add-dir with quoted path containing spaces", () => {
|
|
100
|
-
const r = parseStartInput('튜터 차단 --add-dir "/path with spaces"');
|
|
101
|
-
expect(r.description).toBe("튜터 차단");
|
|
102
|
-
expect(r.sources).toEqual([{ type: "add-dir", path: "/path with spaces" }]);
|
|
103
|
-
});
|
|
104
|
-
it("--add-dir without quotes still works", () => {
|
|
105
|
-
const r = parseStartInput("튜터 차단 --add-dir /simple/path");
|
|
106
|
-
expect(r.description).toBe("튜터 차단");
|
|
107
|
-
expect(r.sources).toEqual([{ type: "add-dir", path: "/simple/path" }]);
|
|
108
|
-
});
|
|
109
|
-
it("--github with quoted url", () => {
|
|
110
|
-
const r = parseStartInput('기능 --github "https://github.com/org/my repo"');
|
|
111
|
-
expect(r.description).toBe("기능");
|
|
112
|
-
expect(r.sources).toEqual([{ type: "github-tarball", url: "https://github.com/org/my repo" }]);
|
|
113
|
-
});
|
|
114
|
-
it("--figma with quoted key", () => {
|
|
115
|
-
const r = parseStartInput('화면 --figma "key with space"');
|
|
116
|
-
expect(r.description).toBe("화면");
|
|
117
|
-
expect(r.sources).toEqual([{ type: "figma-mcp", file_key: "key with space" }]);
|
|
118
|
-
});
|
|
119
|
-
it("--obsidian with quoted path", () => {
|
|
120
|
-
const r = parseStartInput('정책 --obsidian "/vault path/notes"');
|
|
121
|
-
expect(r.description).toBe("정책");
|
|
122
|
-
expect(r.sources).toEqual([{ type: "obsidian-vault", path: "/vault path/notes" }]);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
// ─── Zod validation ───
|
|
126
|
-
describe("loadProjectConfig — Zod validation", () => {
|
|
127
|
-
it("rejects invalid source type and returns empty", () => {
|
|
128
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), `default_sources:\n - type: invalid-type\n path: ./src\n`);
|
|
129
|
-
const config = loadProjectConfig(TMP);
|
|
130
|
-
expect(config.default_sources).toEqual([]);
|
|
131
|
-
});
|
|
132
|
-
it("rejects missing required field (path for add-dir) and returns empty", () => {
|
|
133
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), `default_sources:\n - type: add-dir\n`);
|
|
134
|
-
const config = loadProjectConfig(TMP);
|
|
135
|
-
expect(config.default_sources).toEqual([]);
|
|
136
|
-
});
|
|
137
|
-
it("rejects missing required field (url for github-tarball) and returns empty", () => {
|
|
138
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), `default_sources:\n - type: github-tarball\n`);
|
|
139
|
-
const config = loadProjectConfig(TMP);
|
|
140
|
-
expect(config.default_sources).toEqual([]);
|
|
141
|
-
});
|
|
142
|
-
it("rejects missing required field (file_key for figma-mcp) and returns empty", () => {
|
|
143
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), `default_sources:\n - type: figma-mcp\n`);
|
|
144
|
-
const config = loadProjectConfig(TMP);
|
|
145
|
-
expect(config.default_sources).toEqual([]);
|
|
146
|
-
});
|
|
147
|
-
it("logs warning on validation failure", () => {
|
|
148
|
-
const warnings = [];
|
|
149
|
-
setLogger({
|
|
150
|
-
debug: () => { },
|
|
151
|
-
info: () => { },
|
|
152
|
-
warn: (msg) => { warnings.push(msg); },
|
|
153
|
-
error: () => { },
|
|
154
|
-
});
|
|
155
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), `default_sources:\n - type: unknown-type\n path: ./src\n`);
|
|
156
|
-
loadProjectConfig(TMP);
|
|
157
|
-
expect(warnings.length).toBeGreaterThan(0);
|
|
158
|
-
expect(warnings[0]).toContain("검증 실패");
|
|
159
|
-
setLogger(silentLogger);
|
|
160
|
-
});
|
|
161
|
-
it("accepts valid config with all source types", () => {
|
|
162
|
-
writeFileSync(join(TMP, ".sprint-kit.yaml"), `default_sources:\n` +
|
|
163
|
-
` - type: add-dir\n path: ./src\n description: code\n` +
|
|
164
|
-
` - type: github-tarball\n url: https://github.com/org/repo\n` +
|
|
165
|
-
` - type: figma-mcp\n file_key: abc\n` +
|
|
166
|
-
` - type: obsidian-vault\n path: /vault\n`);
|
|
167
|
-
const config = loadProjectConfig(TMP);
|
|
168
|
-
expect(config.default_sources).toHaveLength(4);
|
|
169
|
-
});
|
|
170
|
-
});
|