@overshift/sfs 1.1.0 → 1.1.2
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/README.md +34 -3
- package/dist/cli/coverage-types.d.ts +1 -0
- package/dist/cli/coverage-types.d.ts.map +1 -1
- package/dist/cli/coverage.d.ts.map +1 -1
- package/dist/cli/coverage.js +14 -10
- package/dist/cli/coverage.js.map +1 -1
- package/dist/cli/help-text.d.ts +6 -0
- package/dist/cli/help-text.d.ts.map +1 -0
- package/dist/cli/help-text.js +50 -0
- package/dist/cli/help-text.js.map +1 -0
- package/dist/cli/init-templates.d.ts +1 -0
- package/dist/cli/init-templates.d.ts.map +1 -1
- package/dist/cli/init-templates.js +25 -0
- package/dist/cli/init-templates.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +21 -16
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/sfsignore.d.ts +5 -0
- package/dist/cli/sfsignore.d.ts.map +1 -0
- package/dist/cli/sfsignore.js +45 -0
- package/dist/cli/sfsignore.js.map +1 -0
- package/dist/cli/story-extractor.d.ts +3 -3
- package/dist/cli/story-extractor.d.ts.map +1 -1
- package/dist/cli/story-extractor.js +29 -16
- package/dist/cli/story-extractor.js.map +1 -1
- package/dist/cli/story-id-pattern.d.ts +10 -0
- package/dist/cli/story-id-pattern.d.ts.map +1 -0
- package/dist/cli/story-id-pattern.js +42 -0
- package/dist/cli/story-id-pattern.js.map +1 -0
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/parser/config-parser.d.ts.map +1 -1
- package/dist/parser/config-parser.js +3 -0
- package/dist/parser/config-parser.js.map +1 -1
- package/dist/parser/types.d.ts +1 -0
- package/dist/parser/types.d.ts.map +1 -1
- package/package.json +8 -3
- package/scripts/postinstall.cjs +12 -0
- package/skills/sfs/from-user-story-to-sfs.md +163 -0
- package/skills/sfs/read-sfs-session-reports.md +234 -0
- package/skills/sfs/run-sfs-session.md +272 -0
- package/skills/sfs/validate-sfs-syntax.md +291 -0
- package/skills/sfs/write-sfs-session-reports.md +206 -0
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ npx sfs init
|
|
|
31
31
|
This creates:
|
|
32
32
|
- `sfs.config.md` - Base configuration
|
|
33
33
|
- `example.sfs` - Example login flow
|
|
34
|
+
- `.sfsignore` - Coverage scan exclusions
|
|
34
35
|
|
|
35
36
|
## CLI Commands
|
|
36
37
|
|
|
@@ -55,11 +56,15 @@ npx sfs validate "stories/**/*.sfs"
|
|
|
55
56
|
### Coverage Report
|
|
56
57
|
|
|
57
58
|
```bash
|
|
58
|
-
npx sfs coverage
|
|
59
|
-
npx sfs coverage
|
|
60
|
-
npx sfs coverage
|
|
59
|
+
npx sfs coverage # Scan entire project, table output
|
|
60
|
+
npx sfs coverage -f json # JSON output for CI pipelines
|
|
61
|
+
npx sfs coverage -f summary # One-line summary
|
|
62
|
+
npx sfs coverage -s docs/stories # Scan only docs/stories directory
|
|
63
|
+
npx sfs coverage --story-pattern "JIRA-\d+" # Custom story ID pattern
|
|
61
64
|
```
|
|
62
65
|
|
|
66
|
+
By default, coverage scans the entire project (respecting `.sfsignore` exclusions). Use `--stories <dir>` to restrict scope.
|
|
67
|
+
|
|
63
68
|
## SFS File Syntax
|
|
64
69
|
|
|
65
70
|
```sfs
|
|
@@ -141,6 +146,32 @@ CLICK login button >> selector:text:Log In # Text selector
|
|
|
141
146
|
| guest | | | Unauthenticated |
|
|
142
147
|
```
|
|
143
148
|
|
|
149
|
+
### .sfsignore
|
|
150
|
+
|
|
151
|
+
Control which directories are excluded from coverage scanning:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
# Comments start with #
|
|
155
|
+
node_modules
|
|
156
|
+
dist
|
|
157
|
+
build
|
|
158
|
+
vendor
|
|
159
|
+
*.log
|
|
160
|
+
docs/archive
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Bare names auto-expand to glob patterns (e.g., `vendor` becomes `**/vendor/**`). Built-in defaults (`node_modules`, `.git`, `dist`, `build`, `coverage`) always apply.
|
|
164
|
+
|
|
165
|
+
### Story ID Pattern
|
|
166
|
+
|
|
167
|
+
Configure custom story ID patterns in `sfs.config.md`:
|
|
168
|
+
|
|
169
|
+
```markdown
|
|
170
|
+
| STORY_ID_PATTERN | JIRA-\d+ |
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Or via CLI: `--story-pattern "JIRA-\d+"`
|
|
174
|
+
|
|
144
175
|
### Environment Layering
|
|
145
176
|
|
|
146
177
|
Configuration files are merged in order:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coverage-types.d.ts","sourceRoot":"","sources":["../../src/cli/coverage-types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"coverage-types.d.ts","sourceRoot":"","sources":["../../src/cli/coverage-types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/cli/coverage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/cli/coverage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,qBAAqB,CAAC;AAY7B,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC7E"}
|
package/dist/cli/coverage.js
CHANGED
|
@@ -3,8 +3,10 @@ import { glob } from "glob";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { extractStoryIdFromFilename, extractStoryTitle, extractStoriesFromMultiFile, deduplicateStories, extractSfsStoryId, } from "./story-extractor.js";
|
|
5
5
|
import { outputResults } from "./coverage-output.js";
|
|
6
|
+
import { loadIgnorePatterns } from "./sfsignore.js";
|
|
7
|
+
import { loadStoryIdPattern } from "./story-id-pattern.js";
|
|
6
8
|
export async function coverageCommand(options) {
|
|
7
|
-
const storiesDir = options.stories ?? "
|
|
9
|
+
const storiesDir = options.stories ?? ".";
|
|
8
10
|
const sfsPattern = options.sfs ?? "**/*.sfs";
|
|
9
11
|
const format = options.format ?? "table";
|
|
10
12
|
// Only show header for non-JSON formats
|
|
@@ -13,7 +15,9 @@ export async function coverageCommand(options) {
|
|
|
13
15
|
console.log();
|
|
14
16
|
}
|
|
15
17
|
try {
|
|
16
|
-
const
|
|
18
|
+
const ignorePatterns = await loadIgnorePatterns();
|
|
19
|
+
const storyIdPattern = await loadStoryIdPattern(undefined, options.storyPattern);
|
|
20
|
+
const result = await calculateCoverage(storiesDir, sfsPattern, ignorePatterns, storyIdPattern);
|
|
17
21
|
outputResults(result, format);
|
|
18
22
|
}
|
|
19
23
|
catch (error) {
|
|
@@ -21,36 +25,36 @@ export async function coverageCommand(options) {
|
|
|
21
25
|
process.exit(1);
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
|
-
async function calculateCoverage(storiesDir, sfsPattern) {
|
|
25
|
-
const singleStoryFiles = await glob(`${storiesDir}
|
|
26
|
-
ignore: [
|
|
28
|
+
async function calculateCoverage(storiesDir, sfsPattern, ignorePatterns, storyIdPattern) {
|
|
29
|
+
const singleStoryFiles = await glob(`${storiesDir}/**/${storyIdPattern.glob}`, {
|
|
30
|
+
ignore: [...ignorePatterns, "**/*.sfs.md"],
|
|
27
31
|
});
|
|
28
32
|
const multiStoryFiles = await glob([
|
|
29
33
|
`${storiesDir}/**/*user-stories*.md`,
|
|
30
34
|
`${storiesDir}/**/*user.stories*.md`,
|
|
31
35
|
`${storiesDir}/**/*user_stories*.md`,
|
|
32
36
|
], {
|
|
33
|
-
ignore:
|
|
37
|
+
ignore: ignorePatterns,
|
|
34
38
|
});
|
|
35
39
|
const sfsFiles = await glob(sfsPattern, {
|
|
36
|
-
ignore:
|
|
40
|
+
ignore: ignorePatterns,
|
|
37
41
|
});
|
|
38
42
|
const stories = [];
|
|
39
43
|
for (const file of singleStoryFiles) {
|
|
40
|
-
const id = extractStoryIdFromFilename(file);
|
|
44
|
+
const id = extractStoryIdFromFilename(file, storyIdPattern.regex);
|
|
41
45
|
if (id) {
|
|
42
46
|
const title = await extractStoryTitle(file);
|
|
43
47
|
stories.push({ id, title, path: file, source: "single" });
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
for (const file of multiStoryFiles) {
|
|
47
|
-
const multiStories = await extractStoriesFromMultiFile(file);
|
|
51
|
+
const multiStories = await extractStoriesFromMultiFile(file, storyIdPattern.regex);
|
|
48
52
|
stories.push(...multiStories);
|
|
49
53
|
}
|
|
50
54
|
const uniqueStories = deduplicateStories(stories);
|
|
51
55
|
const flows = [];
|
|
52
56
|
for (const file of sfsFiles) {
|
|
53
|
-
const storyId = await extractSfsStoryId(file);
|
|
57
|
+
const storyId = await extractSfsStoryId(file, storyIdPattern.regex);
|
|
54
58
|
if (storyId) {
|
|
55
59
|
flows.push({
|
|
56
60
|
storyId,
|
package/dist/cli/coverage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/cli/coverage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,EACjB,2BAA2B,EAC3B,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/cli/coverage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const PROGRAM_HELP_AFTER = "\nQuick Start:\n $ npx sfs init Set up SFS in your project\n $ npx sfs run Run all SFS flow files\n $ npx sfs coverage Show user story coverage report\n\n Run \"npx sfs <command> --help\" for detailed help on each command.\n";
|
|
2
|
+
export declare const COVERAGE_HELP_AFTER = "\nExamples:\n $ sfs coverage Scan entire project (default)\n $ sfs coverage -f json Output as JSON\n $ sfs coverage -f summary Output compact summary\n $ sfs coverage -s stories Restrict scan to stories/ directory\n $ sfs coverage --sfs \"src/**/*.sfs\" Custom SFS file pattern\n $ sfs coverage --story-pattern \"JIRA-\\d+\" Use custom story ID pattern\n\nStory Detection:\n Single-file stories: Files matching US-*.md (or custom pattern prefix)\n Multi-file stories: Files matching *user-stories*.md, *user.stories*.md, *user_stories*.md\n Story ID formats: Headings (## US-001: Title), Bold (**US-001**), Lists (- US-001: Title), Tables (| US-001 | Title |)\n";
|
|
3
|
+
export declare const RUN_HELP_AFTER = "\nExamples:\n $ sfs run Run all *.sfs files\n $ sfs run stories/US-AUTH-001.sfs Run a specific file\n $ sfs run \"stories/**/*.sfs\" Run files matching a glob\n $ sfs run -e local Use local environment config\n $ sfs run -v Show verbose output\n $ sfs run --headed Run browser in headed mode\n";
|
|
4
|
+
export declare const INIT_HELP_AFTER = "\nExamples:\n $ sfs init Create default project files\n $ sfs init --force Overwrite existing files\n $ sfs init --skills-dir .agent/skills Custom skills directory\n\nCreated Files:\n sfs.config.md Configuration (settings, personas, hooks)\n stories/example.sfs Example flow file\n .sfsignore Coverage scan exclusions\n .gitignore Updated with SFS entries\n <skills-dir>/sfs/ SFS skill files for AI assistants\n";
|
|
5
|
+
export declare const VALIDATE_HELP_AFTER = "\nExamples:\n $ sfs validate stories/US-AUTH-001.sfs Validate a single file\n $ sfs validate stories/*.sfs Validate all SFS files\n";
|
|
6
|
+
//# sourceMappingURL=help-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help-text.d.ts","sourceRoot":"","sources":["../../src/cli/help-text.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,0QAO9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,ivBAa/B,CAAC;AAEF,eAAO,MAAM,cAAc,8ZAQ1B,CAAC;AAEF,eAAO,MAAM,eAAe,8dAY3B,CAAC;AAEF,eAAO,MAAM,mBAAmB,kKAI/B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const PROGRAM_HELP_AFTER = `
|
|
2
|
+
Quick Start:
|
|
3
|
+
$ npx sfs init Set up SFS in your project
|
|
4
|
+
$ npx sfs run Run all SFS flow files
|
|
5
|
+
$ npx sfs coverage Show user story coverage report
|
|
6
|
+
|
|
7
|
+
Run "npx sfs <command> --help" for detailed help on each command.
|
|
8
|
+
`;
|
|
9
|
+
export const COVERAGE_HELP_AFTER = `
|
|
10
|
+
Examples:
|
|
11
|
+
$ sfs coverage Scan entire project (default)
|
|
12
|
+
$ sfs coverage -f json Output as JSON
|
|
13
|
+
$ sfs coverage -f summary Output compact summary
|
|
14
|
+
$ sfs coverage -s stories Restrict scan to stories/ directory
|
|
15
|
+
$ sfs coverage --sfs "src/**/*.sfs" Custom SFS file pattern
|
|
16
|
+
$ sfs coverage --story-pattern "JIRA-\\d+" Use custom story ID pattern
|
|
17
|
+
|
|
18
|
+
Story Detection:
|
|
19
|
+
Single-file stories: Files matching US-*.md (or custom pattern prefix)
|
|
20
|
+
Multi-file stories: Files matching *user-stories*.md, *user.stories*.md, *user_stories*.md
|
|
21
|
+
Story ID formats: Headings (## US-001: Title), Bold (**US-001**), Lists (- US-001: Title), Tables (| US-001 | Title |)
|
|
22
|
+
`;
|
|
23
|
+
export const RUN_HELP_AFTER = `
|
|
24
|
+
Examples:
|
|
25
|
+
$ sfs run Run all *.sfs files
|
|
26
|
+
$ sfs run stories/US-AUTH-001.sfs Run a specific file
|
|
27
|
+
$ sfs run "stories/**/*.sfs" Run files matching a glob
|
|
28
|
+
$ sfs run -e local Use local environment config
|
|
29
|
+
$ sfs run -v Show verbose output
|
|
30
|
+
$ sfs run --headed Run browser in headed mode
|
|
31
|
+
`;
|
|
32
|
+
export const INIT_HELP_AFTER = `
|
|
33
|
+
Examples:
|
|
34
|
+
$ sfs init Create default project files
|
|
35
|
+
$ sfs init --force Overwrite existing files
|
|
36
|
+
$ sfs init --skills-dir .agent/skills Custom skills directory
|
|
37
|
+
|
|
38
|
+
Created Files:
|
|
39
|
+
sfs.config.md Configuration (settings, personas, hooks)
|
|
40
|
+
stories/example.sfs Example flow file
|
|
41
|
+
.sfsignore Coverage scan exclusions
|
|
42
|
+
.gitignore Updated with SFS entries
|
|
43
|
+
<skills-dir>/sfs/ SFS skill files for AI assistants
|
|
44
|
+
`;
|
|
45
|
+
export const VALIDATE_HELP_AFTER = `
|
|
46
|
+
Examples:
|
|
47
|
+
$ sfs validate stories/US-AUTH-001.sfs Validate a single file
|
|
48
|
+
$ sfs validate stories/*.sfs Validate all SFS files
|
|
49
|
+
`;
|
|
50
|
+
//# sourceMappingURL=help-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help-text.js","sourceRoot":"","sources":["../../src/cli/help-text.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;CAOjC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;CAalC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;CAQ7B,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;CAY9B,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;CAIlC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const CONFIG_TEMPLATE = "# SFS Configuration\n\n## Settings\n\n| Setting | Value |\n|---------|-------|\n| ENTRYPOINT | http://localhost:3000 |\n| LOCALE | en-US |\n| TIMEOUT | 30000 |\n| ON FAILURE | capture screenshot |\n| PRESERVE STATE ON FAILURE | true |\n\n## Hints\n\n- Add context about your application here\n- Describe the UI layout, navigation patterns\n- Note any authentication requirements\n- Mention relevant test data or fixtures\n\n## Personas\n\n| Persona | Username | Password | Description |\n|---------|----------|----------|-------------|\n| user | testuser@example.com | {{TEST_USER_PASSWORD}} | Standard user account |\n| admin | admin@example.com | {{ADMIN_PASSWORD}} | Administrator with full access |\n| guest | | | Unauthenticated visitor |\n\n## Before Session\n\n```sfs\n# Validate environment\nASSERT file .env exists\nASSERT env-check\n\n# Start services (uncomment as needed)\n# SHELL docker compose up -d\n# WAIT FOR http://localhost:3000 to respond\n```\n\n## After Session\n\n```sfs\n# Collect logs\nSHELL mkdir -p {{RUN_DIR}}\n# SHELL docker compose logs > {{RUN_DIR}}/docker.log\n```\n\n## Before Each\n\n```sfs\n# Reset browser state\nCLEAR cookies\nCLEAR local storage\n```\n\n## After Each\n\n```sfs\n# Log completion\nSHELL echo \"$(date): Completed {{SFS_NAME}}\" >> {{RUN_DIR}}/execution.log\n```\n";
|
|
2
2
|
export declare const EXAMPLE_SFS = "# example.sfs\n# Example: Basic page load verification\n\nSTORY EXAMPLE-001\nENTRYPOINT http://localhost:3000\nLOCALE en-US\n\nHINT Replace with your application context\nHINT Describe the page layout and key elements\n\nON FAILURE capture screenshot and describe visible state\n\nBEFORE THIS\n ASSERT port 3000 is open\n\n--- page-load ---\nAS user\nOBSERVE page has loaded\nOBSERVE main content is visible\n\n--- verify-elements ---\n# Add your verification steps here\nOBSERVE expected elements are present\n\nDONE WHEN page loaded successfully with expected content\n";
|
|
3
3
|
export declare const GITIGNORE_ADDITIONS = "\n# SFS local config (may contain sensitive paths/settings)\nsfs.config.local.md\n\n# SFS run outputs\n/runs/\n/stories/runs/\n*.report.md\n\n# Failure screenshots\n/failures/\n";
|
|
4
|
+
export declare const SFSIGNORE_TEMPLATE = "# .sfsignore - Directories and files to exclude from SFS coverage scanning\n# Syntax: one pattern per line\n# Lines starting with # are comments\n# Bare directory names are auto-expanded to **/name/**\n# Patterns with * or / are used as-is\n\n# Package managers\nnode_modules\n\n# Build outputs\ndist\nbuild\ncoverage\n\n# Version control\n.git\n\n# IDE files\n.vscode\n.idea\n\n# OS files\n.DS_Store\nThumbs.db\n";
|
|
4
5
|
export declare const SFS_SKILLS: string[];
|
|
5
6
|
//# sourceMappingURL=init-templates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-templates.d.ts","sourceRoot":"","sources":["../../src/cli/init-templates.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,0yCA6D3B,CAAC;AAEF,eAAO,MAAM,WAAW,4lBAyBvB,CAAC;AAEF,eAAO,MAAM,mBAAmB,sLAW/B,CAAC;AAEF,eAAO,MAAM,UAAU,UAMtB,CAAC"}
|
|
1
|
+
{"version":3,"file":"init-templates.d.ts","sourceRoot":"","sources":["../../src/cli/init-templates.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,0yCA6D3B,CAAC;AAEF,eAAO,MAAM,WAAW,4lBAyBvB,CAAC;AAEF,eAAO,MAAM,mBAAmB,sLAW/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,kaAwB9B,CAAC;AAEF,eAAO,MAAM,UAAU,UAMtB,CAAC"}
|
|
@@ -98,6 +98,31 @@ sfs.config.local.md
|
|
|
98
98
|
# Failure screenshots
|
|
99
99
|
/failures/
|
|
100
100
|
`;
|
|
101
|
+
export const SFSIGNORE_TEMPLATE = `# .sfsignore - Directories and files to exclude from SFS coverage scanning
|
|
102
|
+
# Syntax: one pattern per line
|
|
103
|
+
# Lines starting with # are comments
|
|
104
|
+
# Bare directory names are auto-expanded to **/name/**
|
|
105
|
+
# Patterns with * or / are used as-is
|
|
106
|
+
|
|
107
|
+
# Package managers
|
|
108
|
+
node_modules
|
|
109
|
+
|
|
110
|
+
# Build outputs
|
|
111
|
+
dist
|
|
112
|
+
build
|
|
113
|
+
coverage
|
|
114
|
+
|
|
115
|
+
# Version control
|
|
116
|
+
.git
|
|
117
|
+
|
|
118
|
+
# IDE files
|
|
119
|
+
.vscode
|
|
120
|
+
.idea
|
|
121
|
+
|
|
122
|
+
# OS files
|
|
123
|
+
.DS_Store
|
|
124
|
+
Thumbs.db
|
|
125
|
+
`;
|
|
101
126
|
export const SFS_SKILLS = [
|
|
102
127
|
"from-user-story-to-sfs.md",
|
|
103
128
|
"write-sfs-session-reports.md",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-templates.js","sourceRoot":"","sources":["../../src/cli/init-templates.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6D9B,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;CAyB1B,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;CAWlC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,2BAA2B;IAC3B,8BAA8B;IAC9B,6BAA6B;IAC7B,wBAAwB;IACxB,oBAAoB;CACrB,CAAC"}
|
|
1
|
+
{"version":3,"file":"init-templates.js","sourceRoot":"","sources":["../../src/cli/init-templates.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6D9B,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;CAyB1B,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;CAWlC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwBjC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,2BAA2B;IAC3B,8BAA8B;IAC9B,6BAA6B;IAC7B,wBAAwB;IACxB,oBAAoB;CACrB,CAAC"}
|
package/dist/cli/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAoBA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ErE"}
|
package/dist/cli/init.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { writeFile, readFile, access, mkdir, readdir, copyFile, } from "fs/promises";
|
|
2
2
|
import { dirname, join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
import { detectMcpIntegration } from "../utils/mcp-detector.js";
|
|
5
|
-
import { CONFIG_TEMPLATE, EXAMPLE_SFS, GITIGNORE_ADDITIONS, SFS_SKILLS, } from "./init-templates.js";
|
|
6
|
+
import { CONFIG_TEMPLATE, EXAMPLE_SFS, GITIGNORE_ADDITIONS, SFSIGNORE_TEMPLATE, SFS_SKILLS, } from "./init-templates.js";
|
|
6
7
|
export async function initCommand(options) {
|
|
7
8
|
console.log(chalk.blue("SFS - Initialize Project"));
|
|
8
9
|
console.log();
|
|
9
10
|
const filesToCreate = [
|
|
10
11
|
{ path: "sfs.config.md", content: CONFIG_TEMPLATE },
|
|
11
12
|
{ path: "stories/example.sfs", content: EXAMPLE_SFS },
|
|
13
|
+
{ path: ".sfsignore", content: SFSIGNORE_TEMPLATE },
|
|
12
14
|
];
|
|
13
15
|
for (const file of filesToCreate) {
|
|
14
16
|
const exists = await fileExists(file.path);
|
|
@@ -57,6 +59,7 @@ export async function initCommand(options) {
|
|
|
57
59
|
console.log(chalk.gray(" 2. Define personas with credentials for your environment"));
|
|
58
60
|
console.log(chalk.gray(" 3. Create your first flow: stories/US-XXX-001.sfs"));
|
|
59
61
|
console.log(chalk.gray(" 4. Run: npx sfs run"));
|
|
62
|
+
console.log(chalk.gray(" 5. Customize .sfsignore to exclude directories from coverage scanning"));
|
|
60
63
|
}
|
|
61
64
|
async function fileExists(path) {
|
|
62
65
|
try {
|
|
@@ -93,51 +96,53 @@ async function detectOrCreateSkillsDir(preferred) {
|
|
|
93
96
|
async function copySkills(destDir, force) {
|
|
94
97
|
console.log();
|
|
95
98
|
console.log(chalk.blue(`Copying SFS skills to ${destDir}...`));
|
|
96
|
-
// Get the SFS package skills directory
|
|
97
|
-
// In development: relative to this file
|
|
98
|
-
// In production: from the package's skills directory
|
|
99
99
|
const packageSkillsDir = await findPackageSkillsDir();
|
|
100
100
|
if (!packageSkillsDir) {
|
|
101
101
|
console.log(chalk.yellow("⚠ Could not locate SFS skills directory"));
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
|
+
// Create sfs/ subdirectory under destination
|
|
105
|
+
await ensureDir(join(destDir, "sfs"));
|
|
104
106
|
for (const skillFile of SFS_SKILLS) {
|
|
105
|
-
const srcPath = join(packageSkillsDir, skillFile);
|
|
106
|
-
const destPath = join(destDir, skillFile);
|
|
107
|
+
const srcPath = join(packageSkillsDir, "sfs", skillFile);
|
|
108
|
+
const destPath = join(destDir, "sfs", skillFile);
|
|
107
109
|
try {
|
|
108
110
|
const srcExists = await fileExists(srcPath);
|
|
109
111
|
if (!srcExists) {
|
|
110
|
-
console.log(chalk.yellow(`⚠ Skill not found:
|
|
112
|
+
console.log(chalk.yellow(`⚠ Skill not found: sfs/${skillFile}`));
|
|
111
113
|
continue;
|
|
112
114
|
}
|
|
113
115
|
const destExists = await fileExists(destPath);
|
|
114
116
|
if (destExists && !force) {
|
|
115
|
-
console.log(chalk.gray(`⊘
|
|
117
|
+
console.log(chalk.gray(`⊘ sfs/${skillFile} already exists`));
|
|
116
118
|
}
|
|
117
119
|
else {
|
|
118
120
|
await copyFile(srcPath, destPath);
|
|
119
|
-
console.log(chalk.green(`✓ Copied
|
|
121
|
+
console.log(chalk.green(`✓ Copied sfs/${skillFile}`));
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
catch (err) {
|
|
123
|
-
console.log(chalk.yellow(`⚠ Could not copy
|
|
125
|
+
console.log(chalk.yellow(`⚠ Could not copy sfs/${skillFile}: ${err instanceof Error ? err.message : "Unknown error"}`));
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
async function findPackageSkillsDir() {
|
|
128
|
-
//
|
|
130
|
+
// Resolve package root from compiled dist/cli/init.js → package root
|
|
131
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
129
132
|
const candidates = [
|
|
133
|
+
// Package-relative: works with npx, global install, local install
|
|
134
|
+
join(packageRoot, "skills"),
|
|
130
135
|
// Development: when running from source
|
|
131
136
|
join(process.cwd(), ".claude", "skills"),
|
|
132
|
-
// Production: when installed as package
|
|
133
|
-
join(process.cwd(), "node_modules", "@
|
|
137
|
+
// Production: when installed as package in project node_modules
|
|
138
|
+
join(process.cwd(), "node_modules", "@overshift", "sfs", "skills"),
|
|
134
139
|
join(process.cwd(), "node_modules", "sfs", "skills"),
|
|
135
140
|
];
|
|
136
141
|
for (const candidate of candidates) {
|
|
137
|
-
|
|
142
|
+
const sfsSubdir = join(candidate, "sfs");
|
|
143
|
+
if (await fileExists(sfsSubdir)) {
|
|
138
144
|
try {
|
|
139
|
-
const files = await readdir(
|
|
140
|
-
// Check if it has at least one of our skill files
|
|
145
|
+
const files = await readdir(sfsSubdir);
|
|
141
146
|
if (files.some((f) => SFS_SKILLS.includes(f))) {
|
|
142
147
|
return candidate;
|
|
143
148
|
}
|
package/dist/cli/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,QAAQ,EACR,MAAM,EACN,KAAK,EACL,OAAO,EACP,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EACL,eAAe,EACf,WAAW,EACX,mBAAmB,EACnB,UAAU,GACX,MAAM,qBAAqB,CAAC;AAO7B,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAoB;IACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,MAAM,aAAa,GAAG;QACpB,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,EAAE;QACnD,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,WAAW,EAAE;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,QAAQ,EACR,MAAM,EACN,KAAK,EACL,OAAO,EACP,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EACL,eAAe,EACf,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,GACX,MAAM,qBAAqB,CAAC;AAO7B,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAoB;IACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,MAAM,aAAa,GAAG;QACpB,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,EAAE;QACnD,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,WAAW,EAAE;QACrD,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE;KACpD,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE3C,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CACV,KAAK,IAAI,CAAC,IAAI,4CAA4C,CAC3D,CACF,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,YAAY,CAAC;QACnC,IAAI,gBAAgB,GAAG,EAAE,CAAC;QAE1B,IAAI,CAAC;YACH,gBAAgB,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,qBAAqB;QACvB,CAAC;QAED,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;YACtD,MAAM,SAAS,CAAC,aAAa,EAAE,gBAAgB,GAAG,mBAAmB,CAAC,CAAC;YACvE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;QACnD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,+BAA+B,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,6BAA6B;IAC7B,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACnE,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC;IACtD,CAAC;IAED,4BAA4B;IAC5B,MAAM,mBAAmB,EAAE,CAAC;IAE5B,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC3B,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,wDAAwD,CAAC,CACrE,CAAC;IACF,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,4DAA4D,CAAC,CACzE,CAAC;IACF,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAClE,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CACR,yEAAyE,CAC1E,CACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,GAAW;IAClC,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QACvB,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,uBAAuB,CACpC,SAAkB;IAElB,iCAAiC;IACjC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,wCAAwC;IACxC,MAAM,UAAU,GAAG,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAEvD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,MAAM,UAAU,GAAG,gBAAgB,CAAC;IACpC,MAAM,SAAS,CAAC,UAAU,CAAC,CAAC;IAC5B,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,OAAe,EAAE,KAAc;IACvD,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,yBAAyB,OAAO,KAAK,CAAC,CAAC,CAAC;IAE/D,MAAM,gBAAgB,GAAG,MAAM,oBAAoB,EAAE,CAAC;IAEtD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,yCAAyC,CAAC,CAAC,CAAC;QACrE,OAAO;IACT,CAAC;IAED,6CAA6C;IAC7C,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAEtC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAEjD,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;YAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,0BAA0B,SAAS,EAAE,CAAC,CAAC,CAAC;gBACjE,SAAS;YACX,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC9C,IAAI,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,SAAS,iBAAiB,CAAC,CAAC,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACN,MAAM,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBAClC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,gBAAgB,SAAS,EAAE,CAAC,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CACV,wBAAwB,SAAS,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAC7F,CACF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,oBAAoB;IACjC,qEAAqE;IACrE,MAAM,WAAW,GAAG,IAAI,CACtB,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EACvC,IAAI,EACJ,IAAI,CACL,CAAC;IAEF,MAAM,UAAU,GAAG;QACjB,kEAAkE;QAClE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC;QAC3B,wCAAwC;QACxC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC;QACxC,gEAAgE;QAChE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,CAAC;QAClE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,CAAC;KACrD,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACzC,IAAI,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;gBACvC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC9C,OAAO,SAAS,CAAC;gBACnB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,6BAA6B;YAC/B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,mBAAmB;IAChC,MAAM,SAAS,GAAG,MAAM,oBAAoB,EAAE,CAAC;IAE/C,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,IAAI,SAAS,CAAC,aAAa,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC;IAC/D,CAAC;SAAM,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CAAC,oDAAoD,CAAC,CACnE,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC,CAAC;IAC7E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CACR,+DAA+D,CAChE,CACF,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function getDefaultIgnorePatterns(): string[];
|
|
2
|
+
export declare function parseSfsIgnore(content: string): string[];
|
|
3
|
+
export declare function normalizeIgnorePattern(pattern: string): string;
|
|
4
|
+
export declare function loadIgnorePatterns(cwd?: string): Promise<string[]>;
|
|
5
|
+
//# sourceMappingURL=sfsignore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sfsignore.d.ts","sourceRoot":"","sources":["../../src/cli/sfsignore.ts"],"names":[],"mappings":"AAGA,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CAQnD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAa9D;AAED,wBAAsB,kBAAkB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYxE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
export function getDefaultIgnorePatterns() {
|
|
4
|
+
return [
|
|
5
|
+
"**/node_modules/**",
|
|
6
|
+
"**/.git/**",
|
|
7
|
+
"**/dist/**",
|
|
8
|
+
"**/build/**",
|
|
9
|
+
"**/coverage/**",
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
export function parseSfsIgnore(content) {
|
|
13
|
+
return content
|
|
14
|
+
.split("\n")
|
|
15
|
+
.map((line) => line.trim())
|
|
16
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
17
|
+
}
|
|
18
|
+
export function normalizeIgnorePattern(pattern) {
|
|
19
|
+
// If it contains glob characters, use as-is
|
|
20
|
+
if (pattern.includes("*")) {
|
|
21
|
+
return pattern;
|
|
22
|
+
}
|
|
23
|
+
// Strip trailing slash for bare directory name check
|
|
24
|
+
const stripped = pattern.replace(/\/+$/, "");
|
|
25
|
+
// If the stripped form contains a path separator, it's a path — use as-is
|
|
26
|
+
if (stripped.includes("/")) {
|
|
27
|
+
return pattern;
|
|
28
|
+
}
|
|
29
|
+
// Bare directory name — wrap in glob
|
|
30
|
+
return `**/${stripped}/**`;
|
|
31
|
+
}
|
|
32
|
+
export async function loadIgnorePatterns(cwd) {
|
|
33
|
+
const defaults = getDefaultIgnorePatterns();
|
|
34
|
+
const dir = cwd ?? process.cwd();
|
|
35
|
+
const sfsignorePath = join(dir, ".sfsignore");
|
|
36
|
+
try {
|
|
37
|
+
const content = await readFile(sfsignorePath, "utf-8");
|
|
38
|
+
const userPatterns = parseSfsIgnore(content).map(normalizeIgnorePattern);
|
|
39
|
+
return [...defaults, ...userPatterns];
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return defaults;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=sfsignore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sfsignore.js","sourceRoot":"","sources":["../../src/cli/sfsignore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,UAAU,wBAAwB;IACtC,OAAO;QACL,oBAAoB;QACpB,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,gBAAgB;KACjB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,OAAO;SACX,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACpD,4CAA4C;IAC5C,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,qDAAqD;IACrD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC7C,0EAA0E;IAC1E,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,qCAAqC;IACrC,OAAO,MAAM,QAAQ,KAAK,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAY;IACnD,MAAM,QAAQ,GAAG,wBAAwB,EAAE,CAAC;IAC5C,MAAM,GAAG,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACjC,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;QACzE,OAAO,CAAC,GAAG,QAAQ,EAAE,GAAG,YAAY,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { UserStory } from "./coverage-types.js";
|
|
2
|
-
export declare function extractStoryIdFromFilename(filePath: string): string | undefined;
|
|
2
|
+
export declare function extractStoryIdFromFilename(filePath: string, storyIdPattern?: RegExp): string | undefined;
|
|
3
3
|
export declare function extractStoryTitle(filePath: string): Promise<string | undefined>;
|
|
4
|
-
export declare function extractStoriesFromMultiFile(filePath: string): Promise<UserStory[]>;
|
|
4
|
+
export declare function extractStoriesFromMultiFile(filePath: string, storyIdPattern?: RegExp): Promise<UserStory[]>;
|
|
5
5
|
export declare function deduplicateStories(stories: UserStory[]): UserStory[];
|
|
6
|
-
export declare function extractSfsStoryId(filePath: string): Promise<string | undefined>;
|
|
6
|
+
export declare function extractSfsStoryId(filePath: string, storyIdPattern?: RegExp): Promise<string | undefined>;
|
|
7
7
|
//# sourceMappingURL=story-extractor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"story-extractor.d.ts","sourceRoot":"","sources":["../../src/cli/story-extractor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"story-extractor.d.ts","sourceRoot":"","sources":["../../src/cli/story-extractor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AASrD,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,GAAG,SAAS,CAQpB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAe7B;AAED,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,SAAS,EAAE,CAAC,CAuFtB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAcpE;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
2
|
import { basename } from "path";
|
|
3
|
-
|
|
3
|
+
import { DEFAULT_STORY_ID_PATTERN } from "./story-id-pattern.js";
|
|
4
|
+
const DEFAULT_RE = new RegExp(DEFAULT_STORY_ID_PATTERN, "i");
|
|
5
|
+
function idRegex(pattern) {
|
|
6
|
+
return pattern ?? DEFAULT_RE;
|
|
7
|
+
}
|
|
8
|
+
export function extractStoryIdFromFilename(filePath, storyIdPattern) {
|
|
4
9
|
const name = basename(filePath, ".md");
|
|
5
|
-
const
|
|
10
|
+
const re = new RegExp(`^(${idRegex(storyIdPattern).source})`, "i");
|
|
11
|
+
const match = name.match(re);
|
|
6
12
|
if (match?.[1]) {
|
|
7
13
|
return match[1].toUpperCase();
|
|
8
14
|
}
|
|
@@ -11,7 +17,7 @@ export function extractStoryIdFromFilename(filePath) {
|
|
|
11
17
|
export async function extractStoryTitle(filePath) {
|
|
12
18
|
try {
|
|
13
19
|
const content = await readFile(filePath, "utf-8");
|
|
14
|
-
const lines = content.split(
|
|
20
|
+
const lines = content.split(/\r?\n/);
|
|
15
21
|
for (const line of lines) {
|
|
16
22
|
const match = line.match(/^#\s+(.+)$/);
|
|
17
23
|
if (match?.[1]) {
|
|
@@ -24,16 +30,18 @@ export async function extractStoryTitle(filePath) {
|
|
|
24
30
|
}
|
|
25
31
|
return undefined;
|
|
26
32
|
}
|
|
27
|
-
export async function extractStoriesFromMultiFile(filePath) {
|
|
33
|
+
export async function extractStoriesFromMultiFile(filePath, storyIdPattern) {
|
|
28
34
|
const stories = [];
|
|
35
|
+
const idSrc = idRegex(storyIdPattern).source;
|
|
29
36
|
try {
|
|
30
37
|
const content = await readFile(filePath, "utf-8");
|
|
31
|
-
const lines = content.split(
|
|
38
|
+
const lines = content.split(/\r?\n/);
|
|
32
39
|
let currentStoryId;
|
|
33
40
|
let currentTitle;
|
|
34
41
|
for (const line of lines) {
|
|
35
|
-
//
|
|
36
|
-
const
|
|
42
|
+
// Headings: ## US-XXX-001: Title
|
|
43
|
+
const headingRe = new RegExp(`^#{1,4}\\s+(${idSrc})[\\s:|-]+(.*)$`, "i");
|
|
44
|
+
const headingMatch = line.match(headingRe);
|
|
37
45
|
if (headingMatch?.[1]) {
|
|
38
46
|
if (currentStoryId) {
|
|
39
47
|
stories.push({
|
|
@@ -47,8 +55,9 @@ export async function extractStoriesFromMultiFile(filePath) {
|
|
|
47
55
|
currentTitle = headingMatch[2]?.trim() || undefined;
|
|
48
56
|
continue;
|
|
49
57
|
}
|
|
50
|
-
//
|
|
51
|
-
const
|
|
58
|
+
// Bold: **US-XXX-001**: Description
|
|
59
|
+
const boldRe = new RegExp(`\\*\\*(${idSrc}):?\\*\\*[\\s:|-]*(.*)$`, "i");
|
|
60
|
+
const boldMatch = line.match(boldRe);
|
|
52
61
|
if (boldMatch?.[1]) {
|
|
53
62
|
if (currentStoryId) {
|
|
54
63
|
stories.push({
|
|
@@ -62,8 +71,9 @@ export async function extractStoriesFromMultiFile(filePath) {
|
|
|
62
71
|
currentTitle = boldMatch[2]?.trim() || undefined;
|
|
63
72
|
continue;
|
|
64
73
|
}
|
|
65
|
-
//
|
|
66
|
-
const
|
|
74
|
+
// List items: - US-XXX-001: Title
|
|
75
|
+
const listRe = new RegExp(`^[\\s]*[-*]\\s+(${idSrc})[\\s:|-]+(.*)$`, "i");
|
|
76
|
+
const listMatch = line.match(listRe);
|
|
67
77
|
if (listMatch?.[1]) {
|
|
68
78
|
stories.push({
|
|
69
79
|
id: listMatch[1].toUpperCase(),
|
|
@@ -73,8 +83,9 @@ export async function extractStoriesFromMultiFile(filePath) {
|
|
|
73
83
|
});
|
|
74
84
|
continue;
|
|
75
85
|
}
|
|
76
|
-
//
|
|
77
|
-
const
|
|
86
|
+
// Table rows: | US-XXX-001 | Title | ... |
|
|
87
|
+
const tableRe = new RegExp(`\\|\\s*(${idSrc})\\s*\\|([^|]*)\\|`, "i");
|
|
88
|
+
const tableMatch = line.match(tableRe);
|
|
78
89
|
if (tableMatch?.[1]) {
|
|
79
90
|
stories.push({
|
|
80
91
|
id: tableMatch[1].toUpperCase(),
|
|
@@ -111,12 +122,14 @@ export function deduplicateStories(stories) {
|
|
|
111
122
|
}
|
|
112
123
|
return [...storyMap.values()];
|
|
113
124
|
}
|
|
114
|
-
export async function extractSfsStoryId(filePath) {
|
|
125
|
+
export async function extractSfsStoryId(filePath, storyIdPattern) {
|
|
126
|
+
const idSrc = idRegex(storyIdPattern).source;
|
|
115
127
|
try {
|
|
116
128
|
const content = await readFile(filePath, "utf-8");
|
|
117
|
-
const lines = content.split(
|
|
129
|
+
const lines = content.split(/\r?\n/);
|
|
130
|
+
const re = new RegExp(`^STORY\\s+(${idSrc})`, "i");
|
|
118
131
|
for (const line of lines) {
|
|
119
|
-
const match = line.match(
|
|
132
|
+
const match = line.match(re);
|
|
120
133
|
if (match?.[1]) {
|
|
121
134
|
return match[1].toUpperCase();
|
|
122
135
|
}
|