@shirokuma-library/shirokuma-docs 0.1.0-alpha.5
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.
Potentially problematic release.
This version of @shirokuma-library/shirokuma-docs might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.en.md +308 -0
- package/README.md +308 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/bin/shirokuma-docs +2 -0
- package/dist/analyzers/details-test-analysis.d.ts +31 -0
- package/dist/analyzers/details-test-analysis.d.ts.map +1 -0
- package/dist/analyzers/details-test-analysis.js +172 -0
- package/dist/analyzers/details-test-analysis.js.map +1 -0
- package/dist/analyzers/feature-map-builder.d.ts +20 -0
- package/dist/analyzers/feature-map-builder.d.ts.map +1 -0
- package/dist/analyzers/feature-map-builder.js +154 -0
- package/dist/analyzers/feature-map-builder.js.map +1 -0
- package/dist/analyzers/feature-map-references.d.ts +34 -0
- package/dist/analyzers/feature-map-references.d.ts.map +1 -0
- package/dist/analyzers/feature-map-references.js +249 -0
- package/dist/analyzers/feature-map-references.js.map +1 -0
- package/dist/analyzers/reference-analyzer.d.ts +95 -0
- package/dist/analyzers/reference-analyzer.d.ts.map +1 -0
- package/dist/analyzers/reference-analyzer.js +372 -0
- package/dist/analyzers/reference-analyzer.js.map +1 -0
- package/dist/commands/adr.d.ts +26 -0
- package/dist/commands/adr.d.ts.map +1 -0
- package/dist/commands/adr.js +129 -0
- package/dist/commands/adr.js.map +1 -0
- package/dist/commands/api-tools.d.ts +83 -0
- package/dist/commands/api-tools.d.ts.map +1 -0
- package/dist/commands/api-tools.js +775 -0
- package/dist/commands/api-tools.js.map +1 -0
- package/dist/commands/coverage.d.ts +139 -0
- package/dist/commands/coverage.d.ts.map +1 -0
- package/dist/commands/coverage.js +481 -0
- package/dist/commands/coverage.js.map +1 -0
- package/dist/commands/deps.d.ts +24 -0
- package/dist/commands/deps.d.ts.map +1 -0
- package/dist/commands/deps.js +211 -0
- package/dist/commands/deps.js.map +1 -0
- package/dist/commands/details-context.d.ts +38 -0
- package/dist/commands/details-context.d.ts.map +1 -0
- package/dist/commands/details-context.js +193 -0
- package/dist/commands/details-context.js.map +1 -0
- package/dist/commands/details-types.d.ts +315 -0
- package/dist/commands/details-types.d.ts.map +1 -0
- package/dist/commands/details-types.js +7 -0
- package/dist/commands/details-types.js.map +1 -0
- package/dist/commands/details.d.ts +24 -0
- package/dist/commands/details.d.ts.map +1 -0
- package/dist/commands/details.js +299 -0
- package/dist/commands/details.js.map +1 -0
- package/dist/commands/discussion-templates.d.ts +26 -0
- package/dist/commands/discussion-templates.d.ts.map +1 -0
- package/dist/commands/discussion-templates.js +270 -0
- package/dist/commands/discussion-templates.js.map +1 -0
- package/dist/commands/discussions.d.ts +31 -0
- package/dist/commands/discussions.d.ts.map +1 -0
- package/dist/commands/discussions.js +743 -0
- package/dist/commands/discussions.js.map +1 -0
- package/dist/commands/feature-map-types.d.ts +294 -0
- package/dist/commands/feature-map-types.d.ts.map +1 -0
- package/dist/commands/feature-map-types.js +8 -0
- package/dist/commands/feature-map-types.js.map +1 -0
- package/dist/commands/feature-map.d.ts +30 -0
- package/dist/commands/feature-map.d.ts.map +1 -0
- package/dist/commands/feature-map.js +137 -0
- package/dist/commands/feature-map.js.map +1 -0
- package/dist/commands/generate.d.ts +16 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +88 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/gh-discussions.d.ts +31 -0
- package/dist/commands/gh-discussions.d.ts.map +1 -0
- package/dist/commands/gh-discussions.js +743 -0
- package/dist/commands/gh-discussions.js.map +1 -0
- package/dist/commands/gh-issues-pr.d.ts +74 -0
- package/dist/commands/gh-issues-pr.d.ts.map +1 -0
- package/dist/commands/gh-issues-pr.js +417 -0
- package/dist/commands/gh-issues-pr.js.map +1 -0
- package/dist/commands/gh-issues.d.ts +90 -0
- package/dist/commands/gh-issues.d.ts.map +1 -0
- package/dist/commands/gh-issues.js +1297 -0
- package/dist/commands/gh-issues.js.map +1 -0
- package/dist/commands/gh-projects.d.ts +54 -0
- package/dist/commands/gh-projects.d.ts.map +1 -0
- package/dist/commands/gh-projects.js +966 -0
- package/dist/commands/gh-projects.js.map +1 -0
- package/dist/commands/gh-repo.d.ts +18 -0
- package/dist/commands/gh-repo.d.ts.map +1 -0
- package/dist/commands/gh-repo.js +253 -0
- package/dist/commands/gh-repo.js.map +1 -0
- package/dist/commands/github-data.d.ts +67 -0
- package/dist/commands/github-data.d.ts.map +1 -0
- package/dist/commands/github-data.js +361 -0
- package/dist/commands/github-data.js.map +1 -0
- package/dist/commands/i18n.d.ts +102 -0
- package/dist/commands/i18n.d.ts.map +1 -0
- package/dist/commands/i18n.js +829 -0
- package/dist/commands/i18n.js.map +1 -0
- package/dist/commands/impact.d.ts +14 -0
- package/dist/commands/impact.d.ts.map +1 -0
- package/dist/commands/impact.js +263 -0
- package/dist/commands/impact.js.map +1 -0
- package/dist/commands/init.d.ts +53 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +429 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/issues-pr.d.ts +74 -0
- package/dist/commands/issues-pr.d.ts.map +1 -0
- package/dist/commands/issues-pr.js +417 -0
- package/dist/commands/issues-pr.js.map +1 -0
- package/dist/commands/issues.d.ts +76 -0
- package/dist/commands/issues.d.ts.map +1 -0
- package/dist/commands/issues.js +1285 -0
- package/dist/commands/issues.js.map +1 -0
- package/dist/commands/link-docs.d.ts +21 -0
- package/dist/commands/link-docs.d.ts.map +1 -0
- package/dist/commands/link-docs.js +990 -0
- package/dist/commands/link-docs.js.map +1 -0
- package/dist/commands/lint-annotations.d.ts +28 -0
- package/dist/commands/lint-annotations.d.ts.map +1 -0
- package/dist/commands/lint-annotations.js +511 -0
- package/dist/commands/lint-annotations.js.map +1 -0
- package/dist/commands/lint-code.d.ts +26 -0
- package/dist/commands/lint-code.d.ts.map +1 -0
- package/dist/commands/lint-code.js +428 -0
- package/dist/commands/lint-code.js.map +1 -0
- package/dist/commands/lint-coverage.d.ts +33 -0
- package/dist/commands/lint-coverage.d.ts.map +1 -0
- package/dist/commands/lint-coverage.js +379 -0
- package/dist/commands/lint-coverage.js.map +1 -0
- package/dist/commands/lint-docs.d.ts +23 -0
- package/dist/commands/lint-docs.d.ts.map +1 -0
- package/dist/commands/lint-docs.js +338 -0
- package/dist/commands/lint-docs.js.map +1 -0
- package/dist/commands/lint-structure.d.ts +38 -0
- package/dist/commands/lint-structure.d.ts.map +1 -0
- package/dist/commands/lint-structure.js +350 -0
- package/dist/commands/lint-structure.js.map +1 -0
- package/dist/commands/lint-tests.d.ts +25 -0
- package/dist/commands/lint-tests.d.ts.map +1 -0
- package/dist/commands/lint-tests.js +105 -0
- package/dist/commands/lint-tests.js.map +1 -0
- package/dist/commands/lint-workflow.d.ts +36 -0
- package/dist/commands/lint-workflow.d.ts.map +1 -0
- package/dist/commands/lint-workflow.js +255 -0
- package/dist/commands/lint-workflow.js.map +1 -0
- package/dist/commands/overview.d.ts +21 -0
- package/dist/commands/overview.d.ts.map +1 -0
- package/dist/commands/overview.js +1300 -0
- package/dist/commands/overview.js.map +1 -0
- package/dist/commands/packages.d.ts +107 -0
- package/dist/commands/packages.d.ts.map +1 -0
- package/dist/commands/packages.js +308 -0
- package/dist/commands/packages.js.map +1 -0
- package/dist/commands/portal-nextjs.d.ts +23 -0
- package/dist/commands/portal-nextjs.d.ts.map +1 -0
- package/dist/commands/portal-nextjs.js +336 -0
- package/dist/commands/portal-nextjs.js.map +1 -0
- package/dist/commands/portal.d.ts +24 -0
- package/dist/commands/portal.d.ts.map +1 -0
- package/dist/commands/portal.js +16 -0
- package/dist/commands/portal.js.map +1 -0
- package/dist/commands/projects.d.ts +54 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +969 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/repo-pairs.d.ts +19 -0
- package/dist/commands/repo-pairs.d.ts.map +1 -0
- package/dist/commands/repo-pairs.js +529 -0
- package/dist/commands/repo-pairs.js.map +1 -0
- package/dist/commands/repo.d.ts +18 -0
- package/dist/commands/repo.d.ts.map +1 -0
- package/dist/commands/repo.js +253 -0
- package/dist/commands/repo.js.map +1 -0
- package/dist/commands/schema.d.ts +49 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +830 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/screenshots.d.ts +203 -0
- package/dist/commands/screenshots.d.ts.map +1 -0
- package/dist/commands/screenshots.js +1234 -0
- package/dist/commands/screenshots.js.map +1 -0
- package/dist/commands/search-index.d.ts +83 -0
- package/dist/commands/search-index.d.ts.map +1 -0
- package/dist/commands/search-index.js +389 -0
- package/dist/commands/search-index.js.map +1 -0
- package/dist/commands/session.d.ts +153 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +1243 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/test-cases-types.d.ts +154 -0
- package/dist/commands/test-cases-types.d.ts.map +1 -0
- package/dist/commands/test-cases-types.js +7 -0
- package/dist/commands/test-cases-types.js.map +1 -0
- package/dist/commands/test-cases.d.ts +28 -0
- package/dist/commands/test-cases.d.ts.map +1 -0
- package/dist/commands/test-cases.js +192 -0
- package/dist/commands/test-cases.js.map +1 -0
- package/dist/commands/typedoc.d.ts +21 -0
- package/dist/commands/typedoc.d.ts.map +1 -0
- package/dist/commands/typedoc.js +192 -0
- package/dist/commands/typedoc.js.map +1 -0
- package/dist/commands/update-skills.d.ts +56 -0
- package/dist/commands/update-skills.d.ts.map +1 -0
- package/dist/commands/update-skills.js +620 -0
- package/dist/commands/update-skills.js.map +1 -0
- package/dist/generators/details-entity-pages.d.ts +40 -0
- package/dist/generators/details-entity-pages.d.ts.map +1 -0
- package/dist/generators/details-entity-pages.js +301 -0
- package/dist/generators/details-entity-pages.js.map +1 -0
- package/dist/generators/details-html.d.ts +23 -0
- package/dist/generators/details-html.d.ts.map +1 -0
- package/dist/generators/details-html.js +324 -0
- package/dist/generators/details-html.js.map +1 -0
- package/dist/generators/details-module-page.d.ts +33 -0
- package/dist/generators/details-module-page.d.ts.map +1 -0
- package/dist/generators/details-module-page.js +408 -0
- package/dist/generators/details-module-page.js.map +1 -0
- package/dist/generators/details-styles.d.ts +39 -0
- package/dist/generators/details-styles.d.ts.map +1 -0
- package/dist/generators/details-styles.js +409 -0
- package/dist/generators/details-styles.js.map +1 -0
- package/dist/generators/feature-map-html.d.ts +66 -0
- package/dist/generators/feature-map-html.d.ts.map +1 -0
- package/dist/generators/feature-map-html.js +569 -0
- package/dist/generators/feature-map-html.js.map +1 -0
- package/dist/generators/feature-map-styles.d.ts +39 -0
- package/dist/generators/feature-map-styles.d.ts.map +1 -0
- package/dist/generators/feature-map-styles.js +449 -0
- package/dist/generators/feature-map-styles.js.map +1 -0
- package/dist/generators/test-cases-hierarchy.d.ts +21 -0
- package/dist/generators/test-cases-hierarchy.d.ts.map +1 -0
- package/dist/generators/test-cases-hierarchy.js +336 -0
- package/dist/generators/test-cases-hierarchy.js.map +1 -0
- package/dist/generators/test-cases-main.d.ts +20 -0
- package/dist/generators/test-cases-main.d.ts.map +1 -0
- package/dist/generators/test-cases-main.js +439 -0
- package/dist/generators/test-cases-main.js.map +1 -0
- package/dist/generators/test-cases-styles.d.ts +64 -0
- package/dist/generators/test-cases-styles.d.ts.map +1 -0
- package/dist/generators/test-cases-styles.js +1277 -0
- package/dist/generators/test-cases-styles.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +517 -0
- package/dist/index.js.map +1 -0
- package/dist/lint/annotation-lint.d.ts +198 -0
- package/dist/lint/annotation-lint.d.ts.map +1 -0
- package/dist/lint/annotation-lint.js +510 -0
- package/dist/lint/annotation-lint.js.map +1 -0
- package/dist/lint/annotation-types.d.ts +161 -0
- package/dist/lint/annotation-types.d.ts.map +1 -0
- package/dist/lint/annotation-types.js +31 -0
- package/dist/lint/annotation-types.js.map +1 -0
- package/dist/lint/code-types.d.ts +135 -0
- package/dist/lint/code-types.d.ts.map +1 -0
- package/dist/lint/code-types.js +25 -0
- package/dist/lint/code-types.js.map +1 -0
- package/dist/lint/coverage-types.d.ts +128 -0
- package/dist/lint/coverage-types.d.ts.map +1 -0
- package/dist/lint/coverage-types.js +24 -0
- package/dist/lint/coverage-types.js.map +1 -0
- package/dist/lint/docs-types.d.ts +214 -0
- package/dist/lint/docs-types.d.ts.map +1 -0
- package/dist/lint/docs-types.js +18 -0
- package/dist/lint/docs-types.js.map +1 -0
- package/dist/lint/formatters/index.d.ts +14 -0
- package/dist/lint/formatters/index.d.ts.map +1 -0
- package/dist/lint/formatters/index.js +28 -0
- package/dist/lint/formatters/index.js.map +1 -0
- package/dist/lint/formatters/json.d.ts +11 -0
- package/dist/lint/formatters/json.d.ts.map +1 -0
- package/dist/lint/formatters/json.js +12 -0
- package/dist/lint/formatters/json.js.map +1 -0
- package/dist/lint/formatters/summary.d.ts +11 -0
- package/dist/lint/formatters/summary.d.ts.map +1 -0
- package/dist/lint/formatters/summary.js +37 -0
- package/dist/lint/formatters/summary.js.map +1 -0
- package/dist/lint/formatters/terminal.d.ts +11 -0
- package/dist/lint/formatters/terminal.d.ts.map +1 -0
- package/dist/lint/formatters/terminal.js +99 -0
- package/dist/lint/formatters/terminal.js.map +1 -0
- package/dist/lint/index.d.ts +18 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint/index.js +103 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/lint/rules/annotation-required.d.ts +35 -0
- package/dist/lint/rules/annotation-required.d.ts.map +1 -0
- package/dist/lint/rules/annotation-required.js +127 -0
- package/dist/lint/rules/annotation-required.js.map +1 -0
- package/dist/lint/rules/code-rules.d.ts +12 -0
- package/dist/lint/rules/code-rules.d.ts.map +1 -0
- package/dist/lint/rules/code-rules.js +11 -0
- package/dist/lint/rules/code-rules.js.map +1 -0
- package/dist/lint/rules/describe-coverage.d.ts +8 -0
- package/dist/lint/rules/describe-coverage.d.ts.map +1 -0
- package/dist/lint/rules/describe-coverage.js +43 -0
- package/dist/lint/rules/describe-coverage.js.map +1 -0
- package/dist/lint/rules/duplicate-testdoc.d.ts +8 -0
- package/dist/lint/rules/duplicate-testdoc.d.ts.map +1 -0
- package/dist/lint/rules/duplicate-testdoc.js +38 -0
- package/dist/lint/rules/duplicate-testdoc.js.map +1 -0
- package/dist/lint/rules/index.d.ts +29 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/index.js +55 -0
- package/dist/lint/rules/index.js.map +1 -0
- package/dist/lint/rules/server-action-structure.d.ts +37 -0
- package/dist/lint/rules/server-action-structure.d.ts.map +1 -0
- package/dist/lint/rules/server-action-structure.js +151 -0
- package/dist/lint/rules/server-action-structure.js.map +1 -0
- package/dist/lint/rules/skipped-test-report.d.ts +11 -0
- package/dist/lint/rules/skipped-test-report.d.ts.map +1 -0
- package/dist/lint/rules/skipped-test-report.js +31 -0
- package/dist/lint/rules/skipped-test-report.js.map +1 -0
- package/dist/lint/rules/structure-rules.d.ts +67 -0
- package/dist/lint/rules/structure-rules.d.ts.map +1 -0
- package/dist/lint/rules/structure-rules.js +615 -0
- package/dist/lint/rules/structure-rules.js.map +1 -0
- package/dist/lint/rules/testdoc-japanese.d.ts +8 -0
- package/dist/lint/rules/testdoc-japanese.d.ts.map +1 -0
- package/dist/lint/rules/testdoc-japanese.js +31 -0
- package/dist/lint/rules/testdoc-japanese.js.map +1 -0
- package/dist/lint/rules/testdoc-min-length.d.ts +8 -0
- package/dist/lint/rules/testdoc-min-length.d.ts.map +1 -0
- package/dist/lint/rules/testdoc-min-length.js +31 -0
- package/dist/lint/rules/testdoc-min-length.js.map +1 -0
- package/dist/lint/rules/testdoc-required.d.ts +8 -0
- package/dist/lint/rules/testdoc-required.d.ts.map +1 -0
- package/dist/lint/rules/testdoc-required.js +27 -0
- package/dist/lint/rules/testdoc-required.js.map +1 -0
- package/dist/lint/rules/workflow-branch-naming.d.ts +20 -0
- package/dist/lint/rules/workflow-branch-naming.d.ts.map +1 -0
- package/dist/lint/rules/workflow-branch-naming.js +85 -0
- package/dist/lint/rules/workflow-branch-naming.js.map +1 -0
- package/dist/lint/rules/workflow-commit-format.d.ts +27 -0
- package/dist/lint/rules/workflow-commit-format.d.ts.map +1 -0
- package/dist/lint/rules/workflow-commit-format.js +92 -0
- package/dist/lint/rules/workflow-commit-format.js.map +1 -0
- package/dist/lint/rules/workflow-issue-fields.d.ts +24 -0
- package/dist/lint/rules/workflow-issue-fields.d.ts.map +1 -0
- package/dist/lint/rules/workflow-issue-fields.js +89 -0
- package/dist/lint/rules/workflow-issue-fields.js.map +1 -0
- package/dist/lint/rules/workflow-main-protection.d.ts +32 -0
- package/dist/lint/rules/workflow-main-protection.d.ts.map +1 -0
- package/dist/lint/rules/workflow-main-protection.js +114 -0
- package/dist/lint/rules/workflow-main-protection.js.map +1 -0
- package/dist/lint/structure-types.d.ts +216 -0
- package/dist/lint/structure-types.d.ts.map +1 -0
- package/dist/lint/structure-types.js +96 -0
- package/dist/lint/structure-types.js.map +1 -0
- package/dist/lint/types.d.ts +154 -0
- package/dist/lint/types.d.ts.map +1 -0
- package/dist/lint/types.js +21 -0
- package/dist/lint/types.js.map +1 -0
- package/dist/lint/workflow-types.d.ts +90 -0
- package/dist/lint/workflow-types.d.ts.map +1 -0
- package/dist/lint/workflow-types.js +7 -0
- package/dist/lint/workflow-types.js.map +1 -0
- package/dist/md/analyzer/index.d.ts +46 -0
- package/dist/md/analyzer/index.d.ts.map +1 -0
- package/dist/md/analyzer/index.js +288 -0
- package/dist/md/analyzer/index.js.map +1 -0
- package/dist/md/builder/index.d.ts +91 -0
- package/dist/md/builder/index.d.ts.map +1 -0
- package/dist/md/builder/index.js +446 -0
- package/dist/md/builder/index.js.map +1 -0
- package/dist/md/cli/analyze.d.ts +11 -0
- package/dist/md/cli/analyze.d.ts.map +1 -0
- package/dist/md/cli/analyze.js +118 -0
- package/dist/md/cli/analyze.js.map +1 -0
- package/dist/md/cli/build.d.ts +11 -0
- package/dist/md/cli/build.d.ts.map +1 -0
- package/dist/md/cli/build.js +74 -0
- package/dist/md/cli/build.js.map +1 -0
- package/dist/md/cli/extract.d.ts +25 -0
- package/dist/md/cli/extract.d.ts.map +1 -0
- package/dist/md/cli/extract.js +230 -0
- package/dist/md/cli/extract.js.map +1 -0
- package/dist/md/cli/index.d.ts +3 -0
- package/dist/md/cli/index.d.ts.map +1 -0
- package/dist/md/cli/index.js +99 -0
- package/dist/md/cli/index.js.map +1 -0
- package/dist/md/cli/lint.d.ts +11 -0
- package/dist/md/cli/lint.d.ts.map +1 -0
- package/dist/md/cli/lint.js +165 -0
- package/dist/md/cli/lint.js.map +1 -0
- package/dist/md/cli/list.d.ts +16 -0
- package/dist/md/cli/list.d.ts.map +1 -0
- package/dist/md/cli/list.js +85 -0
- package/dist/md/cli/list.js.map +1 -0
- package/dist/md/cli/program.d.ts +11 -0
- package/dist/md/cli/program.d.ts.map +1 -0
- package/dist/md/cli/program.js +104 -0
- package/dist/md/cli/program.js.map +1 -0
- package/dist/md/cli/validate.d.ts +8 -0
- package/dist/md/cli/validate.d.ts.map +1 -0
- package/dist/md/cli/validate.js +82 -0
- package/dist/md/cli/validate.js.map +1 -0
- package/dist/md/constants.d.ts +69 -0
- package/dist/md/constants.d.ts.map +1 -0
- package/dist/md/constants.js +69 -0
- package/dist/md/constants.js.map +1 -0
- package/dist/md/extractor/index.d.ts +57 -0
- package/dist/md/extractor/index.d.ts.map +1 -0
- package/dist/md/extractor/index.js +359 -0
- package/dist/md/extractor/index.js.map +1 -0
- package/dist/md/index.d.ts +26 -0
- package/dist/md/index.d.ts.map +1 -0
- package/dist/md/index.js +30 -0
- package/dist/md/index.js.map +1 -0
- package/dist/md/linter/index.d.ts +20 -0
- package/dist/md/linter/index.d.ts.map +1 -0
- package/dist/md/linter/index.js +412 -0
- package/dist/md/linter/index.js.map +1 -0
- package/dist/md/linter/token-optimizer.d.ts +66 -0
- package/dist/md/linter/token-optimizer.d.ts.map +1 -0
- package/dist/md/linter/token-optimizer.js +292 -0
- package/dist/md/linter/token-optimizer.js.map +1 -0
- package/dist/md/lister/index.d.ts +42 -0
- package/dist/md/lister/index.d.ts.map +1 -0
- package/dist/md/lister/index.js +317 -0
- package/dist/md/lister/index.js.map +1 -0
- package/dist/md/parser/heading-numbers.d.ts +43 -0
- package/dist/md/parser/heading-numbers.d.ts.map +1 -0
- package/dist/md/parser/heading-numbers.js +97 -0
- package/dist/md/parser/heading-numbers.js.map +1 -0
- package/dist/md/parser/section-meta.d.ts +50 -0
- package/dist/md/parser/section-meta.d.ts.map +1 -0
- package/dist/md/parser/section-meta.js +212 -0
- package/dist/md/parser/section-meta.js.map +1 -0
- package/dist/md/parser/template.d.ts +56 -0
- package/dist/md/parser/template.d.ts.map +1 -0
- package/dist/md/parser/template.js +122 -0
- package/dist/md/parser/template.js.map +1 -0
- package/dist/md/plugins/loader.d.ts +15 -0
- package/dist/md/plugins/loader.d.ts.map +1 -0
- package/dist/md/plugins/loader.js +80 -0
- package/dist/md/plugins/loader.js.map +1 -0
- package/dist/md/plugins/normalize-headings.d.ts +43 -0
- package/dist/md/plugins/normalize-headings.d.ts.map +1 -0
- package/dist/md/plugins/normalize-headings.js +51 -0
- package/dist/md/plugins/normalize-headings.js.map +1 -0
- package/dist/md/plugins/normalize-whitespace.d.ts +46 -0
- package/dist/md/plugins/normalize-whitespace.d.ts.map +1 -0
- package/dist/md/plugins/normalize-whitespace.js +86 -0
- package/dist/md/plugins/normalize-whitespace.js.map +1 -0
- package/dist/md/plugins/remove-badges.d.ts +36 -0
- package/dist/md/plugins/remove-badges.d.ts.map +1 -0
- package/dist/md/plugins/remove-badges.js +59 -0
- package/dist/md/plugins/remove-badges.js.map +1 -0
- package/dist/md/plugins/remove-comments.d.ts +27 -0
- package/dist/md/plugins/remove-comments.d.ts.map +1 -0
- package/dist/md/plugins/remove-comments.js +40 -0
- package/dist/md/plugins/remove-comments.js.map +1 -0
- package/dist/md/plugins/remove-duplicates.d.ts +40 -0
- package/dist/md/plugins/remove-duplicates.d.ts.map +1 -0
- package/dist/md/plugins/remove-duplicates.js +72 -0
- package/dist/md/plugins/remove-duplicates.js.map +1 -0
- package/dist/md/plugins/remove-internal-links.d.ts +38 -0
- package/dist/md/plugins/remove-internal-links.d.ts.map +1 -0
- package/dist/md/plugins/remove-internal-links.js +66 -0
- package/dist/md/plugins/remove-internal-links.js.map +1 -0
- package/dist/md/plugins/strip-heading-numbers.d.ts +35 -0
- package/dist/md/plugins/strip-heading-numbers.d.ts.map +1 -0
- package/dist/md/plugins/strip-heading-numbers.js +59 -0
- package/dist/md/plugins/strip-heading-numbers.js.map +1 -0
- package/dist/md/plugins/strip-section-meta.d.ts +37 -0
- package/dist/md/plugins/strip-section-meta.d.ts.map +1 -0
- package/dist/md/plugins/strip-section-meta.js +62 -0
- package/dist/md/plugins/strip-section-meta.js.map +1 -0
- package/dist/md/types/config.d.ts +260 -0
- package/dist/md/types/config.d.ts.map +1 -0
- package/dist/md/types/config.js +156 -0
- package/dist/md/types/config.js.map +1 -0
- package/dist/md/types/document.d.ts +37 -0
- package/dist/md/types/document.d.ts.map +1 -0
- package/dist/md/types/document.js +2 -0
- package/dist/md/types/document.js.map +1 -0
- package/dist/md/types/validation.d.ts +107 -0
- package/dist/md/types/validation.d.ts.map +1 -0
- package/dist/md/types/validation.js +2 -0
- package/dist/md/types/validation.js.map +1 -0
- package/dist/md/utils/code-blocks.d.ts +136 -0
- package/dist/md/utils/code-blocks.d.ts.map +1 -0
- package/dist/md/utils/code-blocks.js +178 -0
- package/dist/md/utils/code-blocks.js.map +1 -0
- package/dist/md/utils/config.d.ts +10 -0
- package/dist/md/utils/config.d.ts.map +1 -0
- package/dist/md/utils/config.js +99 -0
- package/dist/md/utils/config.js.map +1 -0
- package/dist/md/utils/file-collector.d.ts +78 -0
- package/dist/md/utils/file-collector.d.ts.map +1 -0
- package/dist/md/utils/file-collector.js +100 -0
- package/dist/md/utils/file-collector.js.map +1 -0
- package/dist/md/utils/markdown.d.ts +18 -0
- package/dist/md/utils/markdown.d.ts.map +1 -0
- package/dist/md/utils/markdown.js +93 -0
- package/dist/md/utils/markdown.js.map +1 -0
- package/dist/md/utils/remark.d.ts +91 -0
- package/dist/md/utils/remark.d.ts.map +1 -0
- package/dist/md/utils/remark.js +125 -0
- package/dist/md/utils/remark.js.map +1 -0
- package/dist/md/utils/tokens.d.ts +9 -0
- package/dist/md/utils/tokens.d.ts.map +1 -0
- package/dist/md/utils/tokens.js +31 -0
- package/dist/md/utils/tokens.js.map +1 -0
- package/dist/md/validator/index.d.ts +40 -0
- package/dist/md/validator/index.d.ts.map +1 -0
- package/dist/md/validator/index.js +289 -0
- package/dist/md/validator/index.js.map +1 -0
- package/dist/parsers/details-jsdoc.d.ts +46 -0
- package/dist/parsers/details-jsdoc.d.ts.map +1 -0
- package/dist/parsers/details-jsdoc.js +262 -0
- package/dist/parsers/details-jsdoc.js.map +1 -0
- package/dist/parsers/details-zod.d.ts +22 -0
- package/dist/parsers/details-zod.d.ts.map +1 -0
- package/dist/parsers/details-zod.js +145 -0
- package/dist/parsers/details-zod.js.map +1 -0
- package/dist/parsers/drizzle-schema.d.ts +92 -0
- package/dist/parsers/drizzle-schema.d.ts.map +1 -0
- package/dist/parsers/drizzle-schema.js +376 -0
- package/dist/parsers/drizzle-schema.js.map +1 -0
- package/dist/parsers/feature-map-tags.d.ts +45 -0
- package/dist/parsers/feature-map-tags.d.ts.map +1 -0
- package/dist/parsers/feature-map-tags.js +292 -0
- package/dist/parsers/feature-map-tags.js.map +1 -0
- package/dist/parsers/feature-map-type-extraction.d.ts +62 -0
- package/dist/parsers/feature-map-type-extraction.d.ts.map +1 -0
- package/dist/parsers/feature-map-type-extraction.js +347 -0
- package/dist/parsers/feature-map-type-extraction.js.map +1 -0
- package/dist/parsers/feature-map-utils.d.ts +34 -0
- package/dist/parsers/feature-map-utils.d.ts.map +1 -0
- package/dist/parsers/feature-map-utils.js +101 -0
- package/dist/parsers/feature-map-utils.js.map +1 -0
- package/dist/parsers/jsdoc-common.d.ts +209 -0
- package/dist/parsers/jsdoc-common.d.ts.map +1 -0
- package/dist/parsers/jsdoc-common.js +655 -0
- package/dist/parsers/jsdoc-common.js.map +1 -0
- package/dist/parsers/jsdoc.d.ts +76 -0
- package/dist/parsers/jsdoc.d.ts.map +1 -0
- package/dist/parsers/jsdoc.js +238 -0
- package/dist/parsers/jsdoc.js.map +1 -0
- package/dist/parsers/screenshot-annotations.d.ts +96 -0
- package/dist/parsers/screenshot-annotations.d.ts.map +1 -0
- package/dist/parsers/screenshot-annotations.js +227 -0
- package/dist/parsers/screenshot-annotations.js.map +1 -0
- package/dist/parsers/test-annotations.d.ts +46 -0
- package/dist/parsers/test-annotations.d.ts.map +1 -0
- package/dist/parsers/test-annotations.js +393 -0
- package/dist/parsers/test-annotations.js.map +1 -0
- package/dist/parsers/test-categorization.d.ts +42 -0
- package/dist/parsers/test-categorization.d.ts.map +1 -0
- package/dist/parsers/test-categorization.js +182 -0
- package/dist/parsers/test-categorization.js.map +1 -0
- package/dist/parsers/zod-schema.d.ts +105 -0
- package/dist/parsers/zod-schema.d.ts.map +1 -0
- package/dist/parsers/zod-schema.js +270 -0
- package/dist/parsers/zod-schema.js.map +1 -0
- package/dist/utils/action-inference.d.ts +23 -0
- package/dist/utils/action-inference.d.ts.map +1 -0
- package/dist/utils/action-inference.js +36 -0
- package/dist/utils/action-inference.js.map +1 -0
- package/dist/utils/app-inference.d.ts +31 -0
- package/dist/utils/app-inference.d.ts.map +1 -0
- package/dist/utils/app-inference.js +41 -0
- package/dist/utils/app-inference.js.map +1 -0
- package/dist/utils/auto-infer.d.ts +93 -0
- package/dist/utils/auto-infer.d.ts.map +1 -0
- package/dist/utils/auto-infer.js +184 -0
- package/dist/utils/auto-infer.js.map +1 -0
- package/dist/utils/config.d.ts +709 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +504 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/file.d.ts +46 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +103 -0
- package/dist/utils/file.js.map +1 -0
- package/dist/utils/formatters.d.ts +111 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/formatters.js +164 -0
- package/dist/utils/formatters.js.map +1 -0
- package/dist/utils/gh-config.d.ts +99 -0
- package/dist/utils/gh-config.d.ts.map +1 -0
- package/dist/utils/gh-config.js +247 -0
- package/dist/utils/gh-config.js.map +1 -0
- package/dist/utils/github.d.ts +98 -0
- package/dist/utils/github.d.ts.map +1 -0
- package/dist/utils/github.js +295 -0
- package/dist/utils/github.js.map +1 -0
- package/dist/utils/html.d.ts +107 -0
- package/dist/utils/html.d.ts.map +1 -0
- package/dist/utils/html.js +376 -0
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/i18n.d.ts +40 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +148 -0
- package/dist/utils/i18n.js.map +1 -0
- package/dist/utils/logger.d.ts +20 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +49 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/project-fields.d.ts +71 -0
- package/dist/utils/project-fields.d.ts.map +1 -0
- package/dist/utils/project-fields.js +318 -0
- package/dist/utils/project-fields.js.map +1 -0
- package/dist/utils/repo-pairs.d.ts +94 -0
- package/dist/utils/repo-pairs.d.ts.map +1 -0
- package/dist/utils/repo-pairs.js +196 -0
- package/dist/utils/repo-pairs.js.map +1 -0
- package/dist/utils/route-inference.d.ts +81 -0
- package/dist/utils/route-inference.d.ts.map +1 -0
- package/dist/utils/route-inference.js +137 -0
- package/dist/utils/route-inference.js.map +1 -0
- package/dist/utils/setup-check.d.ts +34 -0
- package/dist/utils/setup-check.d.ts.map +1 -0
- package/dist/utils/setup-check.js +136 -0
- package/dist/utils/setup-check.js.map +1 -0
- package/dist/utils/shirokumaignore.d.ts +55 -0
- package/dist/utils/shirokumaignore.d.ts.map +1 -0
- package/dist/utils/shirokumaignore.js +94 -0
- package/dist/utils/shirokumaignore.js.map +1 -0
- package/dist/utils/skills-repo.d.ts +353 -0
- package/dist/utils/skills-repo.d.ts.map +1 -0
- package/dist/utils/skills-repo.js +793 -0
- package/dist/utils/skills-repo.js.map +1 -0
- package/dist/utils/status-workflow.d.ts +54 -0
- package/dist/utils/status-workflow.d.ts.map +1 -0
- package/dist/utils/status-workflow.js +103 -0
- package/dist/utils/status-workflow.js.map +1 -0
- package/dist/validators/frontmatter.d.ts +41 -0
- package/dist/validators/frontmatter.d.ts.map +1 -0
- package/dist/validators/frontmatter.js +117 -0
- package/dist/validators/frontmatter.js.map +1 -0
- package/dist/validators/link-checker.d.ts +48 -0
- package/dist/validators/link-checker.d.ts.map +1 -0
- package/dist/validators/link-checker.js +108 -0
- package/dist/validators/link-checker.js.map +1 -0
- package/dist/validators/markdown-structure.d.ts +50 -0
- package/dist/validators/markdown-structure.d.ts.map +1 -0
- package/dist/validators/markdown-structure.js +253 -0
- package/dist/validators/markdown-structure.js.map +1 -0
- package/i18n/cli/en.json +155 -0
- package/i18n/cli/ja.json +155 -0
- package/i18n/discussion/en.json +191 -0
- package/i18n/discussion/ja.json +191 -0
- package/package.json +113 -0
- package/portal/app/api-tools/api-tools-client.tsx +411 -0
- package/portal/app/api-tools/api-tools-document.tsx +240 -0
- package/portal/app/api-tools/page.tsx +56 -0
- package/portal/app/api-tools/swagger-view.tsx +114 -0
- package/portal/app/apps/[appId]/[type]/[module]/[item]/item-tabs-client.tsx +71 -0
- package/portal/app/apps/[appId]/[type]/[module]/[item]/page.tsx +1422 -0
- package/portal/app/apps/[appId]/[type]/[module]/page.tsx +373 -0
- package/portal/app/apps/[appId]/feature-map/feature-map-app-document.tsx +298 -0
- package/portal/app/apps/[appId]/feature-map/page.tsx +224 -0
- package/portal/app/apps/[appId]/i18n/page.tsx +139 -0
- package/portal/app/apps/[appId]/test-cases/page.tsx +840 -0
- package/portal/app/apps/[appId]/tools/[tool]/page.tsx +351 -0
- package/portal/app/apps/[appId]/tools/api-tools-client.tsx +429 -0
- package/portal/app/apps/[appId]/tools/page.tsx +119 -0
- package/portal/app/db-schema/[db]/[table]/page.tsx +235 -0
- package/portal/app/db-schema/[db]/diagram/page.tsx +81 -0
- package/portal/app/db-schema/[db]/page.tsx +148 -0
- package/portal/app/db-schema/db-schema-document.tsx +100 -0
- package/portal/app/db-schema/diagram/client.tsx +211 -0
- package/portal/app/db-schema/diagram/page.tsx +20 -0
- package/portal/app/db-schema/page.tsx +145 -0
- package/portal/app/db-schema/table-detail-document.tsx +710 -0
- package/portal/app/db-schema/table-detail.tsx +747 -0
- package/portal/app/db-schema/table-list-document.tsx +224 -0
- package/portal/app/db-schema/table-list.tsx +247 -0
- package/portal/app/details/[type]/[module]/[item]/item-tabs-client.tsx +71 -0
- package/portal/app/details/[type]/[module]/[item]/page.tsx +1286 -0
- package/portal/app/details/[type]/[module]/page.tsx +884 -0
- package/portal/app/feature-map/feature-map-client.tsx +681 -0
- package/portal/app/feature-map/feature-map-document.tsx +313 -0
- package/portal/app/feature-map/page.tsx +438 -0
- package/portal/app/globals.css +205 -0
- package/portal/app/i18n/[...namespace]/page.tsx +190 -0
- package/portal/app/i18n/i18n-client.tsx +369 -0
- package/portal/app/i18n/page.tsx +339 -0
- package/portal/app/layout.tsx +37 -0
- package/portal/app/overview/page.tsx +65 -0
- package/portal/app/packages/[packageId]/page.tsx +201 -0
- package/portal/app/packages/page.tsx +148 -0
- package/portal/app/page.tsx +568 -0
- package/portal/app/test-cases/[file]/[line]/page.tsx +455 -0
- package/portal/app/test-cases/[file]/[line]/test-detail-document.tsx +335 -0
- package/portal/app/test-cases/[file]/page.tsx +323 -0
- package/portal/app/test-cases/[file]/test-file-document.tsx +335 -0
- package/portal/app/test-cases/page.tsx +546 -0
- package/portal/app/test-cases/test-cases-document.tsx +384 -0
- package/portal/components/code-block.tsx +57 -0
- package/portal/components/document/doc-params-table.tsx +71 -0
- package/portal/components/document/doc-section.tsx +133 -0
- package/portal/components/document/doc-table.tsx +119 -0
- package/portal/components/document/index.ts +9 -0
- package/portal/components/drawflow-er-diagram.tsx +607 -0
- package/portal/components/interactive-er-diagram.tsx +228 -0
- package/portal/components/layout/app-sidebar.tsx +490 -0
- package/portal/components/layout/er-sidebar.tsx +116 -0
- package/portal/components/layout/global-header.tsx +117 -0
- package/portal/components/layout/layout-content.tsx +48 -0
- package/portal/components/markdown-content.tsx +120 -0
- package/portal/components/mermaid-diagram.tsx +83 -0
- package/portal/components/reactflow-er-diagram.tsx +475 -0
- package/portal/components/search-dialog.tsx +268 -0
- package/portal/components/shared/coverage-score-bar.tsx +144 -0
- package/portal/components/swagger/endpoint-accordion.tsx +117 -0
- package/portal/components/swagger/index.ts +7 -0
- package/portal/components/swagger/method-badge.tsx +55 -0
- package/portal/components/swagger/params-table.tsx +78 -0
- package/portal/components/tabs-with-hash.tsx +43 -0
- package/portal/components/test/index.ts +2 -0
- package/portal/components/test/test-bdd-card.tsx +192 -0
- package/portal/components/test/test-matrix.tsx +242 -0
- package/portal/components/ui/accordion.tsx +66 -0
- package/portal/components/ui/badge.tsx +46 -0
- package/portal/components/ui/breadcrumb.tsx +109 -0
- package/portal/components/ui/button.tsx +62 -0
- package/portal/components/ui/card.tsx +92 -0
- package/portal/components/ui/collapsible.tsx +33 -0
- package/portal/components/ui/dialog.tsx +118 -0
- package/portal/components/ui/progress.tsx +28 -0
- package/portal/components/ui/scroll-area.tsx +58 -0
- package/portal/components/ui/sheet.tsx +139 -0
- package/portal/components/ui/table.tsx +116 -0
- package/portal/components/ui/tabs.tsx +66 -0
- package/portal/components.json +21 -0
- package/portal/lib/constants/test-categories.ts +186 -0
- package/portal/lib/data-loader.ts +1181 -0
- package/portal/lib/db-schema-utils.ts +182 -0
- package/portal/lib/format.ts +43 -0
- package/portal/lib/hooks/use-hash-tab.ts +144 -0
- package/portal/lib/path-utils.ts +25 -0
- package/portal/lib/search-index-generator.ts +214 -0
- package/portal/lib/search.ts +126 -0
- package/portal/lib/sidebar-context.tsx +111 -0
- package/portal/lib/types.ts +740 -0
- package/portal/lib/utils.ts +6 -0
- package/portal/next.config.ts +21 -0
- package/portal/package.json +45 -0
- package/portal/postcss.config.mjs +8 -0
- package/portal/tsconfig.json +41 -0
- package/portal/types/drawflow.d.ts +80 -0
- package/templates/README.md +73 -0
- package/templates/coverage.html +367 -0
- package/templates/dark-theme.css +443 -0
- package/templates/discussion/adr.yml.hbs +65 -0
- package/templates/discussion/handovers.yml.hbs +57 -0
- package/templates/discussion/knowledge.yml.hbs +60 -0
- package/templates/discussion/reports.yml.hbs +68 -0
- package/templates/discussion/research.yml.hbs +61 -0
|
@@ -0,0 +1,1243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session command - Unified session management
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* - start: Fetch session context (latest handover + active issues with project fields)
|
|
6
|
+
* - end: Save handover discussion + update issue statuses
|
|
7
|
+
* - check: Detect inconsistencies between Issue state and Project Status
|
|
8
|
+
*
|
|
9
|
+
* Design:
|
|
10
|
+
* - Combines multiple API calls into a single command
|
|
11
|
+
* - Excludes Done/Released items by default
|
|
12
|
+
* - Used internally by starting-session / ending-session skills
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync } from "node:child_process";
|
|
15
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { createLogger } from "../utils/logger.js";
|
|
18
|
+
import { runGhCommand, runGraphQL, getRepoInfo, validateTitle, validateBody, isIssueNumber, parseIssueNumber, } from "../utils/github.js";
|
|
19
|
+
import { loadGhConfig, getDefaultCategory, getDefaultLimit, getMetricsConfig, } from "../utils/gh-config.js";
|
|
20
|
+
import { formatOutput, toTableJson } from "../utils/formatters.js";
|
|
21
|
+
import { fetchOpenPRs, parseLinkedIssues } from "./issues-pr.js";
|
|
22
|
+
import { getIssueId, } from "./issues.js";
|
|
23
|
+
import { getProjectFields, autoSetTimestamps, updateTextField, updateSelectField, generateTimestamp, } from "../utils/project-fields.js";
|
|
24
|
+
import { WORK_STARTED_STATUSES } from "../utils/status-workflow.js";
|
|
25
|
+
import { getProjectId, fetchWorkflows, RECOMMENDED_WORKFLOWS, } from "./projects.js";
|
|
26
|
+
import { validateGitHubSetup, printSetupCheckResults, } from "../utils/setup-check.js";
|
|
27
|
+
/** Statuses to exclude from session start results */
|
|
28
|
+
export const DEFAULT_EXCLUDE_STATUSES = ["Done", "Released"];
|
|
29
|
+
/**
|
|
30
|
+
* Classify inconsistencies from a list of issues with project data.
|
|
31
|
+
* Pure function - no API calls, fully testable.
|
|
32
|
+
*
|
|
33
|
+
* Detects two types of inconsistencies:
|
|
34
|
+
* 1. OPEN issue with terminal status (Done/Released) → should be closed (error)
|
|
35
|
+
* 2. CLOSED issue with work-started status (In Progress/Review/etc.) → status should be Done (error)
|
|
36
|
+
* 3. CLOSED issue with pre-work status (Backlog/Icebox/etc.) → may be intentional (info)
|
|
37
|
+
*/
|
|
38
|
+
export function classifyInconsistencies(issues, doneStatuses = DEFAULT_EXCLUDE_STATUSES) {
|
|
39
|
+
const inconsistencies = [];
|
|
40
|
+
for (const issue of issues) {
|
|
41
|
+
const status = issue.status ?? "";
|
|
42
|
+
const isDoneStatus = doneStatuses.includes(status);
|
|
43
|
+
// OPEN issue with Done/Released status
|
|
44
|
+
if (issue.state === "OPEN" && isDoneStatus) {
|
|
45
|
+
inconsistencies.push({
|
|
46
|
+
number: issue.number,
|
|
47
|
+
title: issue.title,
|
|
48
|
+
url: issue.url,
|
|
49
|
+
issueState: issue.state,
|
|
50
|
+
projectStatus: issue.status,
|
|
51
|
+
severity: "error",
|
|
52
|
+
description: `Issue is OPEN but Project Status is "${issue.status}"`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// CLOSED issue with non-terminal status
|
|
56
|
+
if (issue.state === "CLOSED" && status !== "" && !isDoneStatus) {
|
|
57
|
+
const isWorkStarted = WORK_STARTED_STATUSES.includes(status);
|
|
58
|
+
inconsistencies.push({
|
|
59
|
+
number: issue.number,
|
|
60
|
+
title: issue.title,
|
|
61
|
+
url: issue.url,
|
|
62
|
+
issueState: issue.state,
|
|
63
|
+
projectStatus: issue.status,
|
|
64
|
+
severity: isWorkStarted ? "error" : "info",
|
|
65
|
+
description: `Issue is CLOSED but Project Status is "${issue.status}" (expected Done/Released)`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return inconsistencies;
|
|
70
|
+
}
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// GraphQL Queries - Discussions (Handovers)
|
|
73
|
+
// =============================================================================
|
|
74
|
+
/** Fetch discussion categories to resolve Handovers category ID */
|
|
75
|
+
const GRAPHQL_QUERY_CATEGORIES = `
|
|
76
|
+
query($owner: String!, $name: String!) {
|
|
77
|
+
repository(owner: $owner, name: $name) {
|
|
78
|
+
discussionCategories(first: 20) {
|
|
79
|
+
nodes {
|
|
80
|
+
id
|
|
81
|
+
name
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
/** Fetch recent discussions from a category (for handover filtering) */
|
|
88
|
+
const GRAPHQL_QUERY_RECENT_HANDOVERS = `
|
|
89
|
+
query($owner: String!, $name: String!, $categoryId: ID) {
|
|
90
|
+
repository(owner: $owner, name: $name) {
|
|
91
|
+
discussions(first: 10, categoryId: $categoryId, orderBy: {field: CREATED_AT, direction: DESC}) {
|
|
92
|
+
nodes {
|
|
93
|
+
number
|
|
94
|
+
title
|
|
95
|
+
body
|
|
96
|
+
url
|
|
97
|
+
createdAt
|
|
98
|
+
author { login }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
/** Get repository ID for mutations */
|
|
105
|
+
const GRAPHQL_QUERY_REPO_ID = `
|
|
106
|
+
query($owner: String!, $name: String!) {
|
|
107
|
+
repository(owner: $owner, name: $name) {
|
|
108
|
+
id
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
/** Create a discussion */
|
|
113
|
+
const GRAPHQL_MUTATION_CREATE_DISCUSSION = `
|
|
114
|
+
mutation($repositoryId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
|
|
115
|
+
createDiscussion(input: {repositoryId: $repositoryId, categoryId: $categoryId, title: $title, body: $body}) {
|
|
116
|
+
discussion {
|
|
117
|
+
id
|
|
118
|
+
number
|
|
119
|
+
url
|
|
120
|
+
title
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// GraphQL Queries - Issues with Projects
|
|
127
|
+
// =============================================================================
|
|
128
|
+
/** Fetch issues with project field data */
|
|
129
|
+
const GRAPHQL_QUERY_ISSUES_WITH_PROJECTS = `
|
|
130
|
+
query($owner: String!, $name: String!, $first: Int!, $cursor: String, $states: [IssueState!]) {
|
|
131
|
+
repository(owner: $owner, name: $name) {
|
|
132
|
+
issues(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}, states: $states) {
|
|
133
|
+
pageInfo { hasNextPage endCursor }
|
|
134
|
+
nodes {
|
|
135
|
+
number
|
|
136
|
+
title
|
|
137
|
+
url
|
|
138
|
+
state
|
|
139
|
+
closedAt
|
|
140
|
+
assignees(first: 5) {
|
|
141
|
+
nodes { login }
|
|
142
|
+
}
|
|
143
|
+
labels(first: 10) {
|
|
144
|
+
nodes { name }
|
|
145
|
+
}
|
|
146
|
+
projectItems(first: 5) {
|
|
147
|
+
nodes {
|
|
148
|
+
id
|
|
149
|
+
project { id title }
|
|
150
|
+
status: fieldValueByName(name: "Status") {
|
|
151
|
+
... on ProjectV2ItemFieldSingleSelectValue { name }
|
|
152
|
+
}
|
|
153
|
+
priority: fieldValueByName(name: "Priority") {
|
|
154
|
+
... on ProjectV2ItemFieldSingleSelectValue { name }
|
|
155
|
+
}
|
|
156
|
+
type: fieldValueByName(name: "Type") {
|
|
157
|
+
... on ProjectV2ItemFieldSingleSelectValue { name }
|
|
158
|
+
}
|
|
159
|
+
itemType: fieldValueByName(name: "Item Type") {
|
|
160
|
+
... on ProjectV2ItemFieldSingleSelectValue { name }
|
|
161
|
+
}
|
|
162
|
+
size: fieldValueByName(name: "Size") {
|
|
163
|
+
... on ProjectV2ItemFieldSingleSelectValue { name }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// Helper: Get current GitHub username
|
|
174
|
+
// =============================================================================
|
|
175
|
+
function getCurrentUsername() {
|
|
176
|
+
try {
|
|
177
|
+
const result = spawnSync("gh", ["api", "user", "-q", ".login"], {
|
|
178
|
+
encoding: "utf-8",
|
|
179
|
+
timeout: 10000,
|
|
180
|
+
});
|
|
181
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
182
|
+
return result.stdout.trim();
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// Helper: Resolve Handovers category ID
|
|
192
|
+
// =============================================================================
|
|
193
|
+
function getHandoversCategoryId(owner, repo, categoryName) {
|
|
194
|
+
const result = runGraphQL(GRAPHQL_QUERY_CATEGORIES, {
|
|
195
|
+
owner,
|
|
196
|
+
name: repo,
|
|
197
|
+
});
|
|
198
|
+
if (!result.success)
|
|
199
|
+
return null;
|
|
200
|
+
const nodes = result.data?.data?.repository?.discussionCategories?.nodes ?? [];
|
|
201
|
+
const category = nodes.find((n) => n?.name === categoryName);
|
|
202
|
+
return category?.id ?? null;
|
|
203
|
+
}
|
|
204
|
+
// =============================================================================
|
|
205
|
+
// Helper: Get repository GraphQL ID
|
|
206
|
+
// =============================================================================
|
|
207
|
+
function getRepoId(owner, repo) {
|
|
208
|
+
const result = runGraphQL(GRAPHQL_QUERY_REPO_ID, {
|
|
209
|
+
owner,
|
|
210
|
+
name: repo,
|
|
211
|
+
});
|
|
212
|
+
if (!result.success)
|
|
213
|
+
return null;
|
|
214
|
+
return result.data?.data?.repository?.id ?? null;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Fetch handovers from the Handovers category, optionally filtered by author.
|
|
218
|
+
*
|
|
219
|
+
* @param authorFilter - Username to filter by, or null for all
|
|
220
|
+
* @returns The most recent matching handover, or null
|
|
221
|
+
*/
|
|
222
|
+
function fetchLatestHandover(owner, repo, categoryId, authorFilter) {
|
|
223
|
+
const result = runGraphQL(GRAPHQL_QUERY_RECENT_HANDOVERS, {
|
|
224
|
+
owner,
|
|
225
|
+
name: repo,
|
|
226
|
+
categoryId,
|
|
227
|
+
});
|
|
228
|
+
if (!result.success)
|
|
229
|
+
return null;
|
|
230
|
+
const nodes = result.data?.data?.repository?.discussions?.nodes ?? [];
|
|
231
|
+
// Filter by author if specified
|
|
232
|
+
const filtered = authorFilter
|
|
233
|
+
? nodes.filter((n) => n?.author?.login === authorFilter)
|
|
234
|
+
: nodes;
|
|
235
|
+
const first = filtered[0];
|
|
236
|
+
if (!first?.number)
|
|
237
|
+
return null;
|
|
238
|
+
return {
|
|
239
|
+
number: first.number,
|
|
240
|
+
title: first.title ?? "",
|
|
241
|
+
body: first.body ?? "",
|
|
242
|
+
url: first.url ?? "",
|
|
243
|
+
author: first.author?.login ?? null,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// =============================================================================
|
|
247
|
+
// Helper: Fetch all recent handovers (for --team mode)
|
|
248
|
+
// =============================================================================
|
|
249
|
+
/**
|
|
250
|
+
* Fetch all recent handovers and group by author (latest per author).
|
|
251
|
+
*/
|
|
252
|
+
function fetchTeamHandovers(owner, repo, categoryId) {
|
|
253
|
+
const result = runGraphQL(GRAPHQL_QUERY_RECENT_HANDOVERS, {
|
|
254
|
+
owner,
|
|
255
|
+
name: repo,
|
|
256
|
+
categoryId,
|
|
257
|
+
});
|
|
258
|
+
if (!result.success)
|
|
259
|
+
return [];
|
|
260
|
+
const nodes = result.data?.data?.repository?.discussions?.nodes ?? [];
|
|
261
|
+
// Group by author: keep only the latest per author
|
|
262
|
+
const byAuthor = new Map();
|
|
263
|
+
for (const node of nodes) {
|
|
264
|
+
if (!node?.number)
|
|
265
|
+
continue;
|
|
266
|
+
const author = node.author?.login ?? "unknown";
|
|
267
|
+
if (!byAuthor.has(author)) {
|
|
268
|
+
byAuthor.set(author, {
|
|
269
|
+
number: node.number,
|
|
270
|
+
title: node.title ?? "",
|
|
271
|
+
body: node.body ?? "",
|
|
272
|
+
url: node.url ?? "",
|
|
273
|
+
author,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return Array.from(byAuthor.values());
|
|
278
|
+
}
|
|
279
|
+
function fetchActiveIssues(owner, repo, limit, states = ["OPEN"]) {
|
|
280
|
+
const allIssues = [];
|
|
281
|
+
let cursor = null;
|
|
282
|
+
while (allIssues.length < limit) {
|
|
283
|
+
const fetchCount = Math.min(100, limit - allIssues.length);
|
|
284
|
+
const result = runGraphQL(GRAPHQL_QUERY_ISSUES_WITH_PROJECTS, {
|
|
285
|
+
owner,
|
|
286
|
+
name: repo,
|
|
287
|
+
first: fetchCount,
|
|
288
|
+
cursor: cursor,
|
|
289
|
+
states,
|
|
290
|
+
});
|
|
291
|
+
if (!result.success || !result.data?.data?.repository?.issues)
|
|
292
|
+
break;
|
|
293
|
+
const issuesData = result.data.data.repository.issues;
|
|
294
|
+
const nodes = issuesData.nodes ?? [];
|
|
295
|
+
for (const node of nodes) {
|
|
296
|
+
if (!node?.number)
|
|
297
|
+
continue;
|
|
298
|
+
const projectItems = node.projectItems?.nodes ?? [];
|
|
299
|
+
const matchingItem = projectItems.find((p) => p?.project?.title === repo) ?? projectItems[0];
|
|
300
|
+
const labelNodes = node.labels?.nodes ?? [];
|
|
301
|
+
const issueLabels = labelNodes.map((l) => l?.name ?? "").filter(Boolean);
|
|
302
|
+
const assigneeNodes = node.assignees?.nodes ?? [];
|
|
303
|
+
const issueAssignees = assigneeNodes.map((a) => a?.login ?? "").filter(Boolean);
|
|
304
|
+
allIssues.push({
|
|
305
|
+
number: node.number,
|
|
306
|
+
title: node.title ?? "",
|
|
307
|
+
url: node.url ?? "",
|
|
308
|
+
state: node.state ?? "OPEN",
|
|
309
|
+
closedAt: node.closedAt ?? null,
|
|
310
|
+
labels: issueLabels,
|
|
311
|
+
assignees: issueAssignees,
|
|
312
|
+
status: matchingItem?.status?.name ?? null,
|
|
313
|
+
priority: matchingItem?.priority?.name ?? null,
|
|
314
|
+
type: matchingItem?.type?.name ?? matchingItem?.itemType?.name ?? null,
|
|
315
|
+
size: matchingItem?.size?.name ?? null,
|
|
316
|
+
projectItemId: matchingItem?.id ?? null,
|
|
317
|
+
projectId: matchingItem?.project?.id ?? null,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const pageInfo = issuesData.pageInfo ?? {};
|
|
321
|
+
if (!pageInfo.hasNextPage)
|
|
322
|
+
break;
|
|
323
|
+
cursor = pageInfo.endCursor ?? null;
|
|
324
|
+
}
|
|
325
|
+
return allIssues;
|
|
326
|
+
}
|
|
327
|
+
// =============================================================================
|
|
328
|
+
// Helper: Update issue status in project
|
|
329
|
+
// =============================================================================
|
|
330
|
+
function updateIssueStatus(projectId, itemId, statusValue, projectFields, logger) {
|
|
331
|
+
const statusField = projectFields["Status"];
|
|
332
|
+
if (!statusField) {
|
|
333
|
+
logger.warn("Status field not found in project");
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
const optionId = statusField.options[statusValue];
|
|
337
|
+
if (!optionId) {
|
|
338
|
+
const available = Object.keys(statusField.options).sort().join(", ");
|
|
339
|
+
logger.error(`Invalid Status value '${statusValue}'`);
|
|
340
|
+
logger.info(` Available options: ${available}`);
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
return updateSelectField(projectId, itemId, statusField.id, optionId, logger);
|
|
344
|
+
}
|
|
345
|
+
// =============================================================================
|
|
346
|
+
// PR merge detection (#220)
|
|
347
|
+
// =============================================================================
|
|
348
|
+
/**
|
|
349
|
+
* Issue に紐づくマージ済み PR を検出する。
|
|
350
|
+
*
|
|
351
|
+
* 検出戦略:
|
|
352
|
+
* 1. ブランチ名検索: 現在のブランチに対応する merged PR を探す
|
|
353
|
+
* 2. Issue リンク逆引き: マージ済み PR の body から "Closes #N" 等を検索
|
|
354
|
+
*
|
|
355
|
+
* @returns マージ済み PR 番号。見つからない場合は null
|
|
356
|
+
*/
|
|
357
|
+
export function findMergedPrForIssue(owner, repo, issueNumber, logger) {
|
|
358
|
+
// Strategy 1: ブランチ名ベースの検出
|
|
359
|
+
// 現在のブランチに紐づくマージ済み PR を探す
|
|
360
|
+
try {
|
|
361
|
+
const branchResult = spawnSync("git", ["branch", "--show-current"], {
|
|
362
|
+
encoding: "utf-8",
|
|
363
|
+
timeout: 5000,
|
|
364
|
+
});
|
|
365
|
+
const currentBranch = branchResult.stdout?.trim();
|
|
366
|
+
const baseBranches = ["main", "master", "develop"];
|
|
367
|
+
if (currentBranch && !baseBranches.includes(currentBranch)) {
|
|
368
|
+
const prResult = runGhCommand([
|
|
369
|
+
"pr", "list",
|
|
370
|
+
"--head", currentBranch,
|
|
371
|
+
"--state", "merged",
|
|
372
|
+
"--json", "number",
|
|
373
|
+
"--repo", `${owner}/${repo}`,
|
|
374
|
+
"-L", "1",
|
|
375
|
+
], { silent: true });
|
|
376
|
+
if (prResult.success && Array.isArray(prResult.data) && prResult.data.length > 0) {
|
|
377
|
+
const prNum = prResult.data[0].number;
|
|
378
|
+
logger.debug(`Merged PR #${prNum} found for branch ${currentBranch}`);
|
|
379
|
+
return prNum;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// git コマンド失敗時は次の戦略へ
|
|
385
|
+
}
|
|
386
|
+
// Strategy 2: Issue リンク逆引き
|
|
387
|
+
// 最近マージされた PR の body を検索して、対象 Issue への参照を探す
|
|
388
|
+
const searchResult = runGhCommand([
|
|
389
|
+
"pr", "list",
|
|
390
|
+
"--state", "merged",
|
|
391
|
+
"--search", `#${issueNumber}`,
|
|
392
|
+
"--json", "number,body",
|
|
393
|
+
"--repo", `${owner}/${repo}`,
|
|
394
|
+
"-L", "10",
|
|
395
|
+
], { silent: true });
|
|
396
|
+
if (searchResult.success && Array.isArray(searchResult.data)) {
|
|
397
|
+
for (const pr of searchResult.data) {
|
|
398
|
+
const linked = parseLinkedIssues(pr.body);
|
|
399
|
+
if (linked.includes(issueNumber)) {
|
|
400
|
+
logger.debug(`Merged PR #${pr.number} links to issue #${issueNumber}`);
|
|
401
|
+
return pr.number;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
// =============================================================================
|
|
408
|
+
// Git state helpers
|
|
409
|
+
// =============================================================================
|
|
410
|
+
/**
|
|
411
|
+
* Get current git repository state (branch + uncommitted changes).
|
|
412
|
+
* Returns safe defaults if git commands fail.
|
|
413
|
+
*/
|
|
414
|
+
export function getGitState() {
|
|
415
|
+
let currentBranch = null;
|
|
416
|
+
let uncommittedChanges = [];
|
|
417
|
+
try {
|
|
418
|
+
const branchResult = spawnSync("git", ["branch", "--show-current"], {
|
|
419
|
+
encoding: "utf-8",
|
|
420
|
+
timeout: 5000,
|
|
421
|
+
});
|
|
422
|
+
if (branchResult.status === 0 && branchResult.stdout.trim()) {
|
|
423
|
+
currentBranch = branchResult.stdout.trim();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Git not available or not in a repo - return defaults
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
const statusResult = spawnSync("git", ["status", "--short"], {
|
|
431
|
+
encoding: "utf-8",
|
|
432
|
+
timeout: 5000,
|
|
433
|
+
});
|
|
434
|
+
if (statusResult.status === 0 && statusResult.stdout.trim()) {
|
|
435
|
+
uncommittedChanges = statusResult.stdout
|
|
436
|
+
.trim()
|
|
437
|
+
.split("\n")
|
|
438
|
+
.filter((line) => line.length > 0);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
// Git not available or not in a repo - return defaults
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
currentBranch,
|
|
446
|
+
uncommittedChanges,
|
|
447
|
+
hasUncommittedChanges: uncommittedChanges.length > 0,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
// =============================================================================
|
|
451
|
+
// Session backup helpers (#251)
|
|
452
|
+
// =============================================================================
|
|
453
|
+
const SESSIONS_DIR = ".claude/sessions";
|
|
454
|
+
const BACKUP_SUFFIX = "-precompact-backup.md";
|
|
455
|
+
/**
|
|
456
|
+
* Check for PreCompact session backups in .claude/sessions/.
|
|
457
|
+
* Returns backups sorted by timestamp (most recent first).
|
|
458
|
+
*/
|
|
459
|
+
export function getSessionBackups() {
|
|
460
|
+
if (!existsSync(SESSIONS_DIR))
|
|
461
|
+
return [];
|
|
462
|
+
try {
|
|
463
|
+
const files = readdirSync(SESSIONS_DIR)
|
|
464
|
+
.filter((f) => f.endsWith(BACKUP_SUFFIX))
|
|
465
|
+
.sort()
|
|
466
|
+
.reverse();
|
|
467
|
+
return files.map((f) => ({
|
|
468
|
+
filename: f,
|
|
469
|
+
timestamp: f.replace(BACKUP_SUFFIX, ""),
|
|
470
|
+
content: readFileSync(join(SESSIONS_DIR, f), "utf-8"),
|
|
471
|
+
}));
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Remove all PreCompact session backups from .claude/sessions/.
|
|
479
|
+
* Called after a successful handover to prevent stale backups.
|
|
480
|
+
*
|
|
481
|
+
* @returns Number of files cleaned up
|
|
482
|
+
*/
|
|
483
|
+
export function cleanupSessionBackups() {
|
|
484
|
+
if (!existsSync(SESSIONS_DIR))
|
|
485
|
+
return 0;
|
|
486
|
+
try {
|
|
487
|
+
const files = readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(BACKUP_SUFFIX));
|
|
488
|
+
for (const f of files) {
|
|
489
|
+
unlinkSync(join(SESSIONS_DIR, f));
|
|
490
|
+
}
|
|
491
|
+
return files.length;
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
return 0;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// =============================================================================
|
|
498
|
+
// session start
|
|
499
|
+
// =============================================================================
|
|
500
|
+
async function cmdStart(options, logger) {
|
|
501
|
+
const config = loadGhConfig();
|
|
502
|
+
const repoInfo = getRepoInfo();
|
|
503
|
+
if (!repoInfo) {
|
|
504
|
+
logger.error("Could not determine repository");
|
|
505
|
+
return 1;
|
|
506
|
+
}
|
|
507
|
+
const { owner: repoOwner, name: repo } = repoInfo;
|
|
508
|
+
const owner = options.owner || repoOwner;
|
|
509
|
+
const categoryName = getDefaultCategory(config);
|
|
510
|
+
const limit = getDefaultLimit(config);
|
|
511
|
+
logger.debug(`Repository: ${owner}/${repo}`);
|
|
512
|
+
logger.debug(`Handover category: ${categoryName}`);
|
|
513
|
+
// Team mode: delegate to cmdStartTeam
|
|
514
|
+
if (options.team) {
|
|
515
|
+
return cmdStartTeam(owner, repo, categoryName, limit, options, logger);
|
|
516
|
+
}
|
|
517
|
+
// 1. Resolve author filter for handovers
|
|
518
|
+
let authorFilter = null;
|
|
519
|
+
if (options.all) {
|
|
520
|
+
authorFilter = null;
|
|
521
|
+
}
|
|
522
|
+
else if (options.user) {
|
|
523
|
+
authorFilter = options.user;
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
// Default: filter by current GitHub user
|
|
527
|
+
authorFilter = getCurrentUsername();
|
|
528
|
+
if (authorFilter) {
|
|
529
|
+
logger.debug(`Filtering handovers by author: ${authorFilter}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// 2. Fetch latest handover (filtered)
|
|
533
|
+
let lastHandover = null;
|
|
534
|
+
const categoryId = getHandoversCategoryId(owner, repo, categoryName);
|
|
535
|
+
if (categoryId) {
|
|
536
|
+
lastHandover = fetchLatestHandover(owner, repo, categoryId, authorFilter);
|
|
537
|
+
if (lastHandover) {
|
|
538
|
+
logger.debug(`Found handover #${lastHandover.number} by ${lastHandover.author ?? "unknown"}`);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
logger.debug("No handover found");
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
logger.debug(`Category '${categoryName}' not found, skipping handover`);
|
|
546
|
+
}
|
|
547
|
+
// 2. Fetch active issues with project fields
|
|
548
|
+
const allIssues = fetchActiveIssues(owner, repo, limit);
|
|
549
|
+
// Filter out Done/Released
|
|
550
|
+
const activeIssues = allIssues.filter((i) => !DEFAULT_EXCLUDE_STATUSES.includes(i.status ?? ""));
|
|
551
|
+
logger.debug(`Issues: ${allIssues.length} total, ${activeIssues.length} active`);
|
|
552
|
+
// 3. Fetch open PRs
|
|
553
|
+
const openPRs = fetchOpenPRs(owner, repo);
|
|
554
|
+
logger.debug(`Open PRs: ${openPRs.length}`);
|
|
555
|
+
// 4. Get git state
|
|
556
|
+
const git = getGitState();
|
|
557
|
+
logger.debug(`Branch: ${git.currentBranch ?? "(detached)"}`);
|
|
558
|
+
logger.debug(`Uncommitted changes: ${git.uncommittedChanges.length}`);
|
|
559
|
+
// 4b. Workflow warnings
|
|
560
|
+
const warnings = [];
|
|
561
|
+
const protectedBranches = ["main", "develop"];
|
|
562
|
+
if (git.currentBranch && protectedBranches.includes(git.currentBranch)) {
|
|
563
|
+
warnings.push(`On protected branch "${git.currentBranch}". Create a feature branch before committing.`);
|
|
564
|
+
logger.warn(`Warning: On protected branch "${git.currentBranch}". Create a feature branch before committing.`);
|
|
565
|
+
}
|
|
566
|
+
if (git.hasUncommittedChanges) {
|
|
567
|
+
warnings.push(`${git.uncommittedChanges.length} uncommitted change(s) detected.`);
|
|
568
|
+
}
|
|
569
|
+
// 5. Check for session backups (#251)
|
|
570
|
+
const backups = getSessionBackups();
|
|
571
|
+
if (backups.length > 0) {
|
|
572
|
+
warnings.push(`${backups.length} PreCompact backup(s) found in .claude/sessions/. A previous session may have been interrupted.`);
|
|
573
|
+
logger.warn(`Found ${backups.length} PreCompact backup(s) from interrupted session(s)`);
|
|
574
|
+
}
|
|
575
|
+
// 6. Build output (TableJSON for lists, plain object for single items)
|
|
576
|
+
const issueColumns = ["number", "title", "status", "priority", "type", "size", "assignees", "labels"];
|
|
577
|
+
const prColumns = ["number", "title", "review_decision", "review_thread_count", "review_count"];
|
|
578
|
+
const output = {
|
|
579
|
+
repository: `${owner}/${repo}`,
|
|
580
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
581
|
+
git,
|
|
582
|
+
lastHandover: lastHandover
|
|
583
|
+
? {
|
|
584
|
+
number: lastHandover.number,
|
|
585
|
+
title: lastHandover.title,
|
|
586
|
+
body: lastHandover.body,
|
|
587
|
+
url: lastHandover.url,
|
|
588
|
+
}
|
|
589
|
+
: null,
|
|
590
|
+
backups: backups.length > 0
|
|
591
|
+
? {
|
|
592
|
+
count: backups.length,
|
|
593
|
+
latest: {
|
|
594
|
+
filename: backups[0].filename,
|
|
595
|
+
timestamp: backups[0].timestamp,
|
|
596
|
+
content: backups[0].content,
|
|
597
|
+
},
|
|
598
|
+
}
|
|
599
|
+
: undefined,
|
|
600
|
+
issues: toTableJson(activeIssues.map((i) => ({
|
|
601
|
+
number: i.number,
|
|
602
|
+
title: i.title,
|
|
603
|
+
status: i.status,
|
|
604
|
+
priority: i.priority,
|
|
605
|
+
type: i.type,
|
|
606
|
+
size: i.size,
|
|
607
|
+
assignees: i.assignees,
|
|
608
|
+
labels: i.labels,
|
|
609
|
+
})), issueColumns),
|
|
610
|
+
total_issues: activeIssues.length,
|
|
611
|
+
openPRs: toTableJson(openPRs.map((pr) => ({
|
|
612
|
+
number: pr.number,
|
|
613
|
+
title: pr.title,
|
|
614
|
+
review_decision: pr.reviewDecision,
|
|
615
|
+
review_thread_count: pr.reviewThreadCount,
|
|
616
|
+
review_count: pr.reviewCount,
|
|
617
|
+
})), prColumns),
|
|
618
|
+
};
|
|
619
|
+
const outputFormat = options.format ?? "json";
|
|
620
|
+
const formatted = formatOutput(output, outputFormat);
|
|
621
|
+
console.log(formatted);
|
|
622
|
+
return 0;
|
|
623
|
+
}
|
|
624
|
+
// =============================================================================
|
|
625
|
+
// session start --team (team dashboard)
|
|
626
|
+
// =============================================================================
|
|
627
|
+
/** Group issues by assignee for team view */
|
|
628
|
+
export function groupIssuesByAssignee(issues) {
|
|
629
|
+
const groups = {};
|
|
630
|
+
for (const issue of issues) {
|
|
631
|
+
if (issue.assignees.length === 0) {
|
|
632
|
+
const key = "unassigned";
|
|
633
|
+
if (!groups[key])
|
|
634
|
+
groups[key] = [];
|
|
635
|
+
groups[key].push(issue);
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
for (const assignee of issue.assignees) {
|
|
639
|
+
if (!groups[assignee])
|
|
640
|
+
groups[assignee] = [];
|
|
641
|
+
groups[assignee].push(issue);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return groups;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Team dashboard mode for session start.
|
|
649
|
+
* Shows all members' handovers and issues grouped by assignee.
|
|
650
|
+
*/
|
|
651
|
+
async function cmdStartTeam(owner, repo, categoryName, limit, options, logger) {
|
|
652
|
+
logger.debug("Team dashboard mode");
|
|
653
|
+
// 1. Fetch all team handovers (latest per author)
|
|
654
|
+
const categoryId = getHandoversCategoryId(owner, repo, categoryName);
|
|
655
|
+
let teamHandovers = [];
|
|
656
|
+
if (categoryId) {
|
|
657
|
+
teamHandovers = fetchTeamHandovers(owner, repo, categoryId);
|
|
658
|
+
logger.debug(`Team handovers: ${teamHandovers.length} members`);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
logger.debug(`Category '${categoryName}' not found, skipping handovers`);
|
|
662
|
+
}
|
|
663
|
+
// 2. Fetch active issues with project fields
|
|
664
|
+
const allIssues = fetchActiveIssues(owner, repo, limit);
|
|
665
|
+
const activeIssues = allIssues.filter((i) => !DEFAULT_EXCLUDE_STATUSES.includes(i.status ?? ""));
|
|
666
|
+
// 3. Group issues by assignee
|
|
667
|
+
const issuesByAssignee = groupIssuesByAssignee(activeIssues);
|
|
668
|
+
// 4. Fetch open PRs
|
|
669
|
+
const openPRs = fetchOpenPRs(owner, repo);
|
|
670
|
+
// 5. Build team dashboard output
|
|
671
|
+
const issueColumns = ["number", "title", "status", "priority", "type", "size"];
|
|
672
|
+
const memberDashboards = {};
|
|
673
|
+
// Collect all member names from both handovers and issues
|
|
674
|
+
const allMembers = new Set();
|
|
675
|
+
for (const h of teamHandovers) {
|
|
676
|
+
if (h.author)
|
|
677
|
+
allMembers.add(h.author);
|
|
678
|
+
}
|
|
679
|
+
for (const assignee of Object.keys(issuesByAssignee)) {
|
|
680
|
+
allMembers.add(assignee);
|
|
681
|
+
}
|
|
682
|
+
for (const member of allMembers) {
|
|
683
|
+
const handover = teamHandovers.find((h) => h.author === member);
|
|
684
|
+
const memberIssues = issuesByAssignee[member] ?? [];
|
|
685
|
+
memberDashboards[member] = {
|
|
686
|
+
handover: handover
|
|
687
|
+
? {
|
|
688
|
+
number: handover.number,
|
|
689
|
+
title: handover.title,
|
|
690
|
+
body: handover.body,
|
|
691
|
+
url: handover.url,
|
|
692
|
+
}
|
|
693
|
+
: null,
|
|
694
|
+
issues: toTableJson(memberIssues.map((i) => ({
|
|
695
|
+
number: i.number,
|
|
696
|
+
title: i.title,
|
|
697
|
+
status: i.status,
|
|
698
|
+
priority: i.priority,
|
|
699
|
+
type: i.type,
|
|
700
|
+
size: i.size,
|
|
701
|
+
})), issueColumns),
|
|
702
|
+
issue_count: memberIssues.length,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const prColumns = ["number", "title", "review_decision", "review_thread_count", "review_count"];
|
|
706
|
+
const output = {
|
|
707
|
+
repository: `${owner}/${repo}`,
|
|
708
|
+
mode: "team",
|
|
709
|
+
members: memberDashboards,
|
|
710
|
+
total_members: allMembers.size,
|
|
711
|
+
total_issues: activeIssues.length,
|
|
712
|
+
openPRs: toTableJson(openPRs.map((pr) => ({
|
|
713
|
+
number: pr.number,
|
|
714
|
+
title: pr.title,
|
|
715
|
+
review_decision: pr.reviewDecision,
|
|
716
|
+
review_thread_count: pr.reviewThreadCount,
|
|
717
|
+
review_count: pr.reviewCount,
|
|
718
|
+
})), prColumns),
|
|
719
|
+
};
|
|
720
|
+
const outputFormat = options.format ?? "json";
|
|
721
|
+
const formatted = formatOutput(output, outputFormat);
|
|
722
|
+
console.log(formatted);
|
|
723
|
+
return 0;
|
|
724
|
+
}
|
|
725
|
+
// =============================================================================
|
|
726
|
+
// session end
|
|
727
|
+
// =============================================================================
|
|
728
|
+
async function cmdEnd(options, logger) {
|
|
729
|
+
const config = loadGhConfig();
|
|
730
|
+
const repoInfo = getRepoInfo();
|
|
731
|
+
if (!repoInfo) {
|
|
732
|
+
logger.error("Could not determine repository");
|
|
733
|
+
return 1;
|
|
734
|
+
}
|
|
735
|
+
const { owner: repoOwner, name: repo } = repoInfo;
|
|
736
|
+
const owner = options.owner || repoOwner;
|
|
737
|
+
// Check for uncommitted changes and warn
|
|
738
|
+
const git = getGitState();
|
|
739
|
+
const endWarnings = [];
|
|
740
|
+
if (git.hasUncommittedChanges) {
|
|
741
|
+
endWarnings.push(`${git.uncommittedChanges.length} uncommitted change(s) detected. Consider committing or stashing before ending session.`);
|
|
742
|
+
logger.warn(`Warning: ${git.uncommittedChanges.length} uncommitted change(s) detected. Consider committing or stashing.`);
|
|
743
|
+
}
|
|
744
|
+
// Validate required inputs
|
|
745
|
+
if (!options.title) {
|
|
746
|
+
logger.error("--title is required for session end");
|
|
747
|
+
return 1;
|
|
748
|
+
}
|
|
749
|
+
// Auto-insert [username] into handover title if not already present (#196)
|
|
750
|
+
// Format: "YYYY-MM-DD - summary" → "YYYY-MM-DD [username] - summary"
|
|
751
|
+
let title = options.title;
|
|
752
|
+
if (/^\d{4}-\d{2}-\d{2} - /.test(title) && !title.includes("[")) {
|
|
753
|
+
const username = getCurrentUsername();
|
|
754
|
+
if (username) {
|
|
755
|
+
title = title.replace(/^(\d{4}-\d{2}-\d{2}) - /, `$1 [${username}] - `);
|
|
756
|
+
logger.debug(`Title updated with username: ${title}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const titleError = validateTitle(title);
|
|
760
|
+
if (titleError) {
|
|
761
|
+
logger.error(titleError);
|
|
762
|
+
return 1;
|
|
763
|
+
}
|
|
764
|
+
const bodyError = validateBody(options.body);
|
|
765
|
+
if (bodyError) {
|
|
766
|
+
logger.error(bodyError);
|
|
767
|
+
return 1;
|
|
768
|
+
}
|
|
769
|
+
const updatedIssues = [];
|
|
770
|
+
// 1. Update issue statuses (--done, --review)
|
|
771
|
+
const doneNumbers = (options.done ?? []).filter(isIssueNumber).map(parseIssueNumber);
|
|
772
|
+
const reviewNumbers = (options.review ?? []).filter(isIssueNumber).map(parseIssueNumber);
|
|
773
|
+
if (doneNumbers.length > 0 || reviewNumbers.length > 0) {
|
|
774
|
+
// Fetch issues to get project item IDs
|
|
775
|
+
const limit = getDefaultLimit(config);
|
|
776
|
+
const issues = fetchActiveIssues(owner, repo, limit);
|
|
777
|
+
// Get project fields once
|
|
778
|
+
const projectIds = new Set();
|
|
779
|
+
for (const issue of issues) {
|
|
780
|
+
if (issue.projectId)
|
|
781
|
+
projectIds.add(issue.projectId);
|
|
782
|
+
}
|
|
783
|
+
// Cache project fields per project
|
|
784
|
+
const fieldsCache = {};
|
|
785
|
+
for (const pid of projectIds) {
|
|
786
|
+
fieldsCache[pid] = getProjectFields(pid);
|
|
787
|
+
}
|
|
788
|
+
// Update Done issues
|
|
789
|
+
for (const num of doneNumbers) {
|
|
790
|
+
const issue = issues.find((i) => i.number === num);
|
|
791
|
+
if (!issue?.projectItemId || !issue?.projectId) {
|
|
792
|
+
logger.warn(`Issue #${num}: not found in project, skipping status update`);
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
const fields = fieldsCache[issue.projectId] ?? {};
|
|
796
|
+
if (updateIssueStatus(issue.projectId, issue.projectItemId, "Done", fields, logger)) {
|
|
797
|
+
updatedIssues.push({ number: num, status: "Done" });
|
|
798
|
+
logger.success(`Issue #${num} → Done`);
|
|
799
|
+
// Auto-set timestamp (#342) - reuse cached fields
|
|
800
|
+
autoSetTimestamps(issue.projectId, issue.projectItemId, "Done", fields, logger);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
// Update Review issues (auto-promote to Done if PR is already merged, #220)
|
|
804
|
+
for (const num of reviewNumbers) {
|
|
805
|
+
const issue = issues.find((i) => i.number === num);
|
|
806
|
+
if (!issue?.projectItemId || !issue?.projectId) {
|
|
807
|
+
logger.warn(`Issue #${num}: not found in project, skipping status update`);
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const fields = fieldsCache[issue.projectId] ?? {};
|
|
811
|
+
// Check if a merged PR exists for this issue (#220)
|
|
812
|
+
const mergedPr = findMergedPrForIssue(owner, repo, num, logger);
|
|
813
|
+
const targetStatus = mergedPr ? "Done" : "Review";
|
|
814
|
+
if (updateIssueStatus(issue.projectId, issue.projectItemId, targetStatus, fields, logger)) {
|
|
815
|
+
updatedIssues.push({ number: num, status: targetStatus });
|
|
816
|
+
if (mergedPr) {
|
|
817
|
+
logger.success(`Issue #${num} → Done (PR #${mergedPr} merged)`);
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
logger.success(`Issue #${num} → Review`);
|
|
821
|
+
}
|
|
822
|
+
// Auto-set timestamp (#342) - reuse cached fields
|
|
823
|
+
autoSetTimestamps(issue.projectId, issue.projectItemId, targetStatus, fields, logger);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
// 2. Create handover discussion
|
|
828
|
+
const categoryName = getDefaultCategory(config);
|
|
829
|
+
const categoryId = getHandoversCategoryId(owner, repo, categoryName);
|
|
830
|
+
let handoverOutput = null;
|
|
831
|
+
if (categoryId) {
|
|
832
|
+
const repoId = getRepoId(owner, repo);
|
|
833
|
+
if (!repoId) {
|
|
834
|
+
logger.error("Could not get repository ID");
|
|
835
|
+
return 1;
|
|
836
|
+
}
|
|
837
|
+
const result = runGraphQL(GRAPHQL_MUTATION_CREATE_DISCUSSION, {
|
|
838
|
+
repositoryId: repoId,
|
|
839
|
+
categoryId: categoryId,
|
|
840
|
+
title: title,
|
|
841
|
+
body: options.body ?? "",
|
|
842
|
+
});
|
|
843
|
+
if (!result.success) {
|
|
844
|
+
logger.error("Failed to create handover discussion");
|
|
845
|
+
return 1;
|
|
846
|
+
}
|
|
847
|
+
const discussion = result.data?.data?.createDiscussion?.discussion;
|
|
848
|
+
if (discussion?.number) {
|
|
849
|
+
handoverOutput = {
|
|
850
|
+
number: discussion.number,
|
|
851
|
+
title: discussion.title ?? title,
|
|
852
|
+
url: discussion.url ?? "",
|
|
853
|
+
};
|
|
854
|
+
logger.success(`Handover saved: #${discussion.number}`);
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
logger.error("Failed to create handover discussion");
|
|
858
|
+
return 1;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
logger.error(`Category '${categoryName}' not found. Cannot create handover.`);
|
|
863
|
+
logger.info("Ensure Discussions are enabled and Handovers category exists.");
|
|
864
|
+
return 1;
|
|
865
|
+
}
|
|
866
|
+
// 3. Clean up session backups (#251)
|
|
867
|
+
const cleanedBackups = cleanupSessionBackups();
|
|
868
|
+
if (cleanedBackups > 0) {
|
|
869
|
+
logger.debug(`Cleaned up ${cleanedBackups} PreCompact backup(s)`);
|
|
870
|
+
}
|
|
871
|
+
// 4. Build output
|
|
872
|
+
const output = {
|
|
873
|
+
warnings: endWarnings.length > 0 ? endWarnings : undefined,
|
|
874
|
+
handover: handoverOutput,
|
|
875
|
+
updatedIssues,
|
|
876
|
+
cleanedBackups: cleanedBackups > 0 ? cleanedBackups : undefined,
|
|
877
|
+
};
|
|
878
|
+
console.log(JSON.stringify(output, null, 2));
|
|
879
|
+
return 0;
|
|
880
|
+
}
|
|
881
|
+
// =============================================================================
|
|
882
|
+
// session check - Metrics helpers (#342)
|
|
883
|
+
// =============================================================================
|
|
884
|
+
/** Fetch Text field values for all project items (batch, 1 query per project) */
|
|
885
|
+
const GRAPHQL_QUERY_PROJECT_ITEM_TEXT_VALUES = `
|
|
886
|
+
query($projectId: ID!, $first: Int!) {
|
|
887
|
+
node(id: $projectId) {
|
|
888
|
+
... on ProjectV2 {
|
|
889
|
+
items(first: $first) {
|
|
890
|
+
nodes {
|
|
891
|
+
id
|
|
892
|
+
fieldValues(first: 20) {
|
|
893
|
+
nodes {
|
|
894
|
+
... on ProjectV2ItemFieldTextValue {
|
|
895
|
+
text
|
|
896
|
+
field { ... on ProjectV2Field { name } }
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
`;
|
|
906
|
+
/**
|
|
907
|
+
* Batch-fetch Text field values for all items in a project.
|
|
908
|
+
* Returns map: itemId → { fieldName → textValue }
|
|
909
|
+
*/
|
|
910
|
+
function fetchItemTextFieldValues(projectId) {
|
|
911
|
+
const result = runGraphQL(GRAPHQL_QUERY_PROJECT_ITEM_TEXT_VALUES, {
|
|
912
|
+
projectId,
|
|
913
|
+
first: 100,
|
|
914
|
+
});
|
|
915
|
+
if (!result.success)
|
|
916
|
+
return {};
|
|
917
|
+
const itemMap = {};
|
|
918
|
+
const items = result.data?.data?.node?.items?.nodes ?? [];
|
|
919
|
+
for (const item of items) {
|
|
920
|
+
if (!item?.id)
|
|
921
|
+
continue;
|
|
922
|
+
const textValues = {};
|
|
923
|
+
const fieldValues = item.fieldValues?.nodes ?? [];
|
|
924
|
+
for (const fv of fieldValues) {
|
|
925
|
+
if (fv?.field?.name && fv?.text) {
|
|
926
|
+
textValues[fv.field.name] = fv.text;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (Object.keys(textValues).length > 0) {
|
|
930
|
+
itemMap[item.id] = textValues;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return itemMap;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Classify metrics-related inconsistencies.
|
|
937
|
+
* Pure function - no API calls, fully testable.
|
|
938
|
+
*
|
|
939
|
+
* Detects:
|
|
940
|
+
* 1. Done/Released issues missing Completed At timestamp
|
|
941
|
+
* 2. In Progress issues that are stale (In Progress At older than threshold)
|
|
942
|
+
*/
|
|
943
|
+
export function classifyMetricsInconsistencies(issues, textFieldValues, metricsConfig, now) {
|
|
944
|
+
const inconsistencies = [];
|
|
945
|
+
const currentTime = now ?? new Date();
|
|
946
|
+
const mapping = metricsConfig.statusToDateMapping ?? {};
|
|
947
|
+
const staleThreshold = metricsConfig.staleThresholdDays ?? 14;
|
|
948
|
+
for (const issue of issues) {
|
|
949
|
+
const status = issue.status ?? "";
|
|
950
|
+
const itemId = issue.projectItemId;
|
|
951
|
+
if (!itemId)
|
|
952
|
+
continue;
|
|
953
|
+
const textValues = textFieldValues[itemId] ?? {};
|
|
954
|
+
// Done/Released issues missing Completed At timestamp
|
|
955
|
+
if (["Done", "Released"].includes(status)) {
|
|
956
|
+
const completedAtField = mapping["Done"];
|
|
957
|
+
if (completedAtField && !textValues[completedAtField]) {
|
|
958
|
+
inconsistencies.push({
|
|
959
|
+
number: issue.number,
|
|
960
|
+
title: issue.title,
|
|
961
|
+
url: issue.url,
|
|
962
|
+
issueState: issue.state,
|
|
963
|
+
projectStatus: issue.status,
|
|
964
|
+
severity: "info",
|
|
965
|
+
description: `Metrics: Missing '${completedAtField}' timestamp for ${status} issue`,
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
// In Progress issues - stale check
|
|
970
|
+
if (status === "In Progress") {
|
|
971
|
+
const inProgressAtField = mapping["In Progress"];
|
|
972
|
+
if (inProgressAtField && textValues[inProgressAtField]) {
|
|
973
|
+
const inProgressAt = new Date(textValues[inProgressAtField]);
|
|
974
|
+
if (!isNaN(inProgressAt.getTime())) {
|
|
975
|
+
const daysSinceStart = Math.floor((currentTime.getTime() - inProgressAt.getTime()) / (1000 * 60 * 60 * 24));
|
|
976
|
+
if (daysSinceStart > staleThreshold) {
|
|
977
|
+
inconsistencies.push({
|
|
978
|
+
number: issue.number,
|
|
979
|
+
title: issue.title,
|
|
980
|
+
url: issue.url,
|
|
981
|
+
issueState: issue.state,
|
|
982
|
+
projectStatus: issue.status,
|
|
983
|
+
severity: "info",
|
|
984
|
+
description: `Metrics: In Progress for ${daysSinceStart} days (stale threshold: ${staleThreshold} days)`,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return inconsistencies;
|
|
992
|
+
}
|
|
993
|
+
// =============================================================================
|
|
994
|
+
// session check - Integrity check
|
|
995
|
+
// =============================================================================
|
|
996
|
+
const GRAPHQL_MUTATION_CLOSE_ISSUE = `
|
|
997
|
+
mutation($issueId: ID!, $stateReason: IssueClosedStateReason) {
|
|
998
|
+
closeIssue(input: {issueId: $issueId, stateReason: $stateReason}) {
|
|
999
|
+
issue { id number state }
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
`;
|
|
1003
|
+
function closeIssueById(issueId) {
|
|
1004
|
+
const result = runGraphQL(GRAPHQL_MUTATION_CLOSE_ISSUE, {
|
|
1005
|
+
issueId,
|
|
1006
|
+
stateReason: "COMPLETED",
|
|
1007
|
+
});
|
|
1008
|
+
return result.success;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Check for inconsistencies between GitHub Issue state and Project Status.
|
|
1012
|
+
*
|
|
1013
|
+
* Detects two types of inconsistencies:
|
|
1014
|
+
* 1. OPEN issues with terminal Project Status (Done/Released) → fix: close issue
|
|
1015
|
+
* 2. CLOSED issues with active Project Status (Review/In Progress/etc.) → fix: set status to Done
|
|
1016
|
+
*/
|
|
1017
|
+
async function cmdCheck(options, logger) {
|
|
1018
|
+
// --setup mode: validate GitHub manual setup items (#345)
|
|
1019
|
+
if (options.setup) {
|
|
1020
|
+
const setupResult = validateGitHubSetup(logger);
|
|
1021
|
+
if (!setupResult)
|
|
1022
|
+
return 1;
|
|
1023
|
+
printSetupCheckResults(setupResult, logger);
|
|
1024
|
+
console.log(JSON.stringify(setupResult, null, 2));
|
|
1025
|
+
return setupResult.summary.missing > 0 ? 1 : 0;
|
|
1026
|
+
}
|
|
1027
|
+
const config = loadGhConfig();
|
|
1028
|
+
const repoInfo = getRepoInfo();
|
|
1029
|
+
if (!repoInfo) {
|
|
1030
|
+
logger.error("Could not determine repository");
|
|
1031
|
+
return 1;
|
|
1032
|
+
}
|
|
1033
|
+
const { owner: repoOwner, name: repo } = repoInfo;
|
|
1034
|
+
const owner = options.owner || repoOwner;
|
|
1035
|
+
const limit = getDefaultLimit(config);
|
|
1036
|
+
logger.debug(`Repository: ${owner}/${repo}`);
|
|
1037
|
+
// 1. Fetch both OPEN and CLOSED issues in a single query
|
|
1038
|
+
const allIssues = fetchActiveIssues(owner, repo, limit, ["OPEN", "CLOSED"]);
|
|
1039
|
+
logger.debug(`Issues fetched: ${allIssues.length}`);
|
|
1040
|
+
// 2. Classify inconsistencies (pure function)
|
|
1041
|
+
const inconsistencies = classifyInconsistencies(allIssues);
|
|
1042
|
+
logger.debug(`Inconsistencies found: ${inconsistencies.length}`);
|
|
1043
|
+
// 3. Fix if --fix is specified
|
|
1044
|
+
const fixes = [];
|
|
1045
|
+
if (options.fix && inconsistencies.length > 0) {
|
|
1046
|
+
// Pre-fetch project fields for status updates (needed for CLOSED + active status fixes)
|
|
1047
|
+
const projectFieldsCache = {};
|
|
1048
|
+
const errorItems = inconsistencies.filter((i) => i.severity === "error");
|
|
1049
|
+
for (const item of errorItems) {
|
|
1050
|
+
if (item.issueState === "OPEN") {
|
|
1051
|
+
// OPEN + Done/Released → close the issue
|
|
1052
|
+
logger.info(`Closing #${item.number}: ${item.description}`);
|
|
1053
|
+
const issueId = getIssueId(owner, repo, item.number);
|
|
1054
|
+
if (!issueId) {
|
|
1055
|
+
fixes.push({
|
|
1056
|
+
number: item.number,
|
|
1057
|
+
action: "close",
|
|
1058
|
+
success: false,
|
|
1059
|
+
error: "Could not resolve issue ID",
|
|
1060
|
+
});
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
const success = closeIssueById(issueId);
|
|
1064
|
+
fixes.push({
|
|
1065
|
+
number: item.number,
|
|
1066
|
+
action: "close",
|
|
1067
|
+
success,
|
|
1068
|
+
error: success ? undefined : "GraphQL mutation failed",
|
|
1069
|
+
});
|
|
1070
|
+
if (success) {
|
|
1071
|
+
logger.success(`Closed #${item.number}`);
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
logger.error(`Failed to close #${item.number}`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
else if (item.issueState === "CLOSED") {
|
|
1078
|
+
// CLOSED + active status → update status to Done
|
|
1079
|
+
logger.info(`Updating #${item.number} status to Done: ${item.description}`);
|
|
1080
|
+
const issueData = allIssues.find((i) => i.number === item.number);
|
|
1081
|
+
if (!issueData?.projectItemId || !issueData?.projectId) {
|
|
1082
|
+
fixes.push({
|
|
1083
|
+
number: item.number,
|
|
1084
|
+
action: "update-status",
|
|
1085
|
+
success: false,
|
|
1086
|
+
error: "Could not resolve project item ID",
|
|
1087
|
+
});
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
// Cache project fields per project
|
|
1091
|
+
if (!projectFieldsCache[issueData.projectId]) {
|
|
1092
|
+
projectFieldsCache[issueData.projectId] = getProjectFields(issueData.projectId);
|
|
1093
|
+
}
|
|
1094
|
+
const fields = projectFieldsCache[issueData.projectId];
|
|
1095
|
+
const success = updateIssueStatus(issueData.projectId, issueData.projectItemId, "Done", fields, logger);
|
|
1096
|
+
fixes.push({
|
|
1097
|
+
number: item.number,
|
|
1098
|
+
action: "update-status",
|
|
1099
|
+
success,
|
|
1100
|
+
error: success ? undefined : "Failed to update project status",
|
|
1101
|
+
});
|
|
1102
|
+
if (success) {
|
|
1103
|
+
logger.success(`Updated #${item.number} status to Done`);
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
logger.error(`Failed to update #${item.number} status`);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
// 3b. Metrics check (#342) - if metrics enabled, detect missing timestamps and stale issues
|
|
1112
|
+
const metricsConfig = getMetricsConfig(config);
|
|
1113
|
+
if (metricsConfig.enabled) {
|
|
1114
|
+
logger.debug("Metrics check enabled");
|
|
1115
|
+
// Batch-fetch text field values per project
|
|
1116
|
+
const projectIds = new Set();
|
|
1117
|
+
for (const issue of allIssues) {
|
|
1118
|
+
if (issue.projectId)
|
|
1119
|
+
projectIds.add(issue.projectId);
|
|
1120
|
+
}
|
|
1121
|
+
let allTextFieldValues = {};
|
|
1122
|
+
for (const pid of projectIds) {
|
|
1123
|
+
const values = fetchItemTextFieldValues(pid);
|
|
1124
|
+
allTextFieldValues = { ...allTextFieldValues, ...values };
|
|
1125
|
+
}
|
|
1126
|
+
// Classify metrics inconsistencies (pure function)
|
|
1127
|
+
const metricsIssues = classifyMetricsInconsistencies(allIssues, allTextFieldValues, metricsConfig);
|
|
1128
|
+
logger.debug(`Metrics inconsistencies: ${metricsIssues.length}`);
|
|
1129
|
+
// Add to main inconsistencies list
|
|
1130
|
+
inconsistencies.push(...metricsIssues);
|
|
1131
|
+
// Fix: backfill timestamps for Done issues missing Completed At
|
|
1132
|
+
if (options.fix && metricsIssues.length > 0) {
|
|
1133
|
+
const mapping = metricsConfig.statusToDateMapping ?? {};
|
|
1134
|
+
for (const item of metricsIssues) {
|
|
1135
|
+
if (!item.description.startsWith("Metrics: Missing"))
|
|
1136
|
+
continue;
|
|
1137
|
+
const issueData = allIssues.find((i) => i.number === item.number);
|
|
1138
|
+
if (!issueData?.projectItemId || !issueData?.projectId)
|
|
1139
|
+
continue;
|
|
1140
|
+
const completedAtField = mapping["Done"];
|
|
1141
|
+
if (!completedAtField)
|
|
1142
|
+
continue;
|
|
1143
|
+
const pf = getProjectFields(issueData.projectId);
|
|
1144
|
+
const fieldInfo = pf[completedAtField];
|
|
1145
|
+
if (!fieldInfo || fieldInfo.type !== "TEXT")
|
|
1146
|
+
continue;
|
|
1147
|
+
// Use closedAt if available, otherwise current timestamp
|
|
1148
|
+
const ts = issueData.closedAt ?? generateTimestamp();
|
|
1149
|
+
const success = updateTextField(issueData.projectId, issueData.projectItemId, fieldInfo.id, ts);
|
|
1150
|
+
fixes.push({
|
|
1151
|
+
number: item.number,
|
|
1152
|
+
action: "backfill-timestamp",
|
|
1153
|
+
success,
|
|
1154
|
+
error: success ? undefined : "Failed to set Text field",
|
|
1155
|
+
});
|
|
1156
|
+
if (success) {
|
|
1157
|
+
logger.success(`Backfilled ${completedAtField} for #${item.number} (${ts})`);
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
logger.error(`Failed to backfill ${completedAtField} for #${item.number}`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
// 4. Check automation status (#250)
|
|
1166
|
+
let automationStatus;
|
|
1167
|
+
const projectId = getProjectId(owner);
|
|
1168
|
+
if (projectId) {
|
|
1169
|
+
const workflows = fetchWorkflows(projectId);
|
|
1170
|
+
if (workflows.length > 0) {
|
|
1171
|
+
const workflowSummary = workflows.map((w) => ({
|
|
1172
|
+
name: w.name,
|
|
1173
|
+
enabled: w.enabled,
|
|
1174
|
+
recommended: RECOMMENDED_WORKFLOWS.includes(w.name),
|
|
1175
|
+
}));
|
|
1176
|
+
const missingRecommended = workflowSummary
|
|
1177
|
+
.filter((w) => w.recommended && !w.enabled)
|
|
1178
|
+
.map((w) => w.name);
|
|
1179
|
+
automationStatus = {
|
|
1180
|
+
checked: true,
|
|
1181
|
+
workflows: workflowSummary,
|
|
1182
|
+
missing_recommended: missingRecommended,
|
|
1183
|
+
};
|
|
1184
|
+
if (missingRecommended.length > 0) {
|
|
1185
|
+
logger.warn(`Recommended automations disabled: ${missingRecommended.join(", ")}`);
|
|
1186
|
+
logger.info("Enable via: GitHub Project Settings > Workflows");
|
|
1187
|
+
}
|
|
1188
|
+
else {
|
|
1189
|
+
logger.debug("All recommended automations are enabled");
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
// 5. Build output
|
|
1194
|
+
const output = {
|
|
1195
|
+
repository: `${owner}/${repo}`,
|
|
1196
|
+
inconsistencies,
|
|
1197
|
+
fixes,
|
|
1198
|
+
automations: automationStatus,
|
|
1199
|
+
summary: {
|
|
1200
|
+
total_checked: allIssues.length,
|
|
1201
|
+
total_inconsistencies: inconsistencies.length,
|
|
1202
|
+
errors: inconsistencies.filter((i) => i.severity === "error").length,
|
|
1203
|
+
info: inconsistencies.filter((i) => i.severity === "info").length,
|
|
1204
|
+
fixed: fixes.filter((f) => f.success).length,
|
|
1205
|
+
fix_failures: fixes.filter((f) => !f.success).length,
|
|
1206
|
+
},
|
|
1207
|
+
};
|
|
1208
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1209
|
+
// Exit code 1 if: unfixed inconsistencies remain, or fix attempts failed
|
|
1210
|
+
if (output.summary.fix_failures > 0)
|
|
1211
|
+
return 1;
|
|
1212
|
+
if (!options.fix && output.summary.errors > 0)
|
|
1213
|
+
return 1;
|
|
1214
|
+
return 0;
|
|
1215
|
+
}
|
|
1216
|
+
// =============================================================================
|
|
1217
|
+
// Main Command Handler
|
|
1218
|
+
// =============================================================================
|
|
1219
|
+
export async function sessionCommand(action, options) {
|
|
1220
|
+
const logger = createLogger(options.verbose);
|
|
1221
|
+
logger.debug(`Action: ${action}`);
|
|
1222
|
+
logger.debug(`Owner: ${options.owner ?? "(auto)"}`);
|
|
1223
|
+
let exitCode = 0;
|
|
1224
|
+
switch (action) {
|
|
1225
|
+
case "start":
|
|
1226
|
+
exitCode = await cmdStart(options, logger);
|
|
1227
|
+
break;
|
|
1228
|
+
case "end":
|
|
1229
|
+
exitCode = await cmdEnd(options, logger);
|
|
1230
|
+
break;
|
|
1231
|
+
case "check":
|
|
1232
|
+
exitCode = await cmdCheck(options, logger);
|
|
1233
|
+
break;
|
|
1234
|
+
default:
|
|
1235
|
+
logger.error(`Unknown action: ${action}`);
|
|
1236
|
+
logger.info("Available actions: start, end, check");
|
|
1237
|
+
exitCode = 1;
|
|
1238
|
+
}
|
|
1239
|
+
if (exitCode !== 0) {
|
|
1240
|
+
process.exit(exitCode);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
//# sourceMappingURL=session.js.map
|