@webpieces/pr-gate 0.4.682 → 0.4.684
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.684",
|
|
4
4
|
"description": "Gated PR system: 3-point squash-merge, merge validation gate, and red/yellow/green PR dashboard. Standalone scripts, no Nx dependency required.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"directory": "packages/tooling/pr-gate"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@webpieces/rules-config": "0.4.
|
|
18
|
+
"@webpieces/rules-config": "0.4.684",
|
|
19
19
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
20
20
|
"inversify": "7.10.4",
|
|
21
21
|
"reflect-metadata": "0.2.2"
|
|
@@ -30,7 +30,7 @@ export declare class WorktreeCleanupSection {
|
|
|
30
30
|
* classification order the branch prompt uses, because it is literally the same verdict on the
|
|
31
31
|
* branch the worktree holds.
|
|
32
32
|
*
|
|
33
|
-
* Deliberately excluded: LOCKED (a
|
|
33
|
+
* Deliberately excluded: LOCKED (a lock is standing and we cannot say whose), CURRENT (removing your own cwd
|
|
34
34
|
* is not a thing to offer), DETACHED (no branch, so nothing to archive and nothing to judge) and
|
|
35
35
|
* PRUNABLE (already provably dead — it is in the auto-reap list, not this one).
|
|
36
36
|
*/
|
|
@@ -43,7 +43,7 @@ let WorktreeCleanupSection = class WorktreeCleanupSection {
|
|
|
43
43
|
* classification order the branch prompt uses, because it is literally the same verdict on the
|
|
44
44
|
* branch the worktree holds.
|
|
45
45
|
*
|
|
46
|
-
* Deliberately excluded: LOCKED (a
|
|
46
|
+
* Deliberately excluded: LOCKED (a lock is standing and we cannot say whose), CURRENT (removing your own cwd
|
|
47
47
|
* is not a thing to offer), DETACHED (no branch, so nothing to archive and nothing to judge) and
|
|
48
48
|
* PRUNABLE (already provably dead — it is in the auto-reap list, not this one).
|
|
49
49
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree-cleanup.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/worktree-cleanup.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n MutationVerb,\n DeletableWorktree,\n MergedBranchesService,\n ReapedWorktree,\n WorktreeReapResult,\n WorktreeReaper,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n CLASSIFICATION_PRUNABLE,\n PROMPTABLE_CLASSIFICATIONS,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * The WORKTREE half of `wp-cleanup` — the verdicts, the reap and the human-facing text.\n *\n * Split out of CleanupCommand rather than bolted onto it because the two halves answer different\n * questions: a branch is a ref, a worktree is a DIRECTORY OF FILES, and the second one needs its own\n * report (paths, not just names), its own restore command (`git worktree add`, not `git checkout -b`)\n * and its own spared vocabulary (locked, detached, \"you are standing in it\"). Keeping them in one class\n * meant every string had to hedge about which kind of thing it was talking about.\n *\n * WHY worktrees are reaped BEFORE branches in wp-cleanup: a worktree HOLDS its branch, so that branch\n * is spared as `in-use` with \"remove that worktree before deleting the branch\". Reaping the worktree\n * first is what makes the branch reapable — the reap takes the branch with it, and the branch pass that\n * follows recomputes its verdicts against the post-removal truth. Run the other way round, every\n * worktree-held branch survives forever, which is exactly the deadlock this change exists to break.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class WorktreeCleanupSection {\n constructor(\n private readonly mergedBranches: MergedBranchesService,\n private readonly reaper: WorktreeReaper,\n ) {}\n\n /**\n * FRESH verdicts — never the cache on disk. Same rule the branch half follows: the cached file is\n * deliberately allowed to go stale, which is fine for BLOCKING a `git worktree add` and never fine\n * for removing a directory, since the tree may have gained uncommitted work since it was written.\n */\n verdicts(repoRoot: string): DeletableWorktree[] {\n return this.mergedBranches.computeMergedBranches(repoRoot).worktrees;\n }\n\n provablyDead(verdicts: DeletableWorktree[]): DeletableWorktree[] {\n return verdicts.filter((tree: DeletableWorktree): boolean => tree.deletable);\n }\n\n /**\n * The spared worktrees a human can meaningfully rule on, grouped most-safe first — the same\n * classification order the branch prompt uses, because it is literally the same verdict on the\n * branch the worktree holds.\n *\n * Deliberately excluded: LOCKED (a human already said do not touch), CURRENT (removing your own cwd\n * is not a thing to offer), DETACHED (no branch, so nothing to archive and nothing to judge) and\n * PRUNABLE (already provably dead — it is in the auto-reap list, not this one).\n */\n promptable(verdicts: DeletableWorktree[]): DeletableWorktree[] {\n const out: DeletableWorktree[] = [];\n for (const classification of PROMPTABLE_CLASSIFICATIONS) {\n for (const tree of verdicts) {\n if (!tree.deletable && tree.classification === classification) out.push(tree);\n }\n }\n return out;\n }\n\n reap(\n repoRoot: string,\n verb: MutationVerb,\n targets: DeletableWorktree[],\n retention: string,\n ): WorktreeReapResult {\n return this.reaper.reapWorktrees(repoRoot, process.cwd(), verb, targets, retention);\n }\n\n report(result: WorktreeReapResult): string {\n if (result.reaped.length === 0 && result.failed.length === 0) return '';\n\n let out = '\\n' + SEP + `🌲 Removed ${String(result.reaped.length)} dead worktree(s)\\n` + SEP + '\\n';\n for (const entry of result.reaped) out += this.reapedLine(entry);\n\n if (result.failed.length > 0) {\n out += `\\n⚠️ ${String(result.failed.length)} worktree(s) could not be removed:\\n`;\n for (const entry of result.failed) out += ` ✗ ${entry.path} — ${entry.error}\\n`;\n }\n // Printed on success too: removing a worktree deletes real files, and a human who cannot see\n // how to undo that has to take it on trust — which is precisely what nobody should have to do.\n out += '\\nEvery removal is logged in .webpieces/logs/branch-mutations.log (phase REAP_WORKTREE)\\n'\n + 'with the `recover=` command that brings back both the directory and its branch.\\n';\n return out;\n }\n\n private reapedLine(entry: ReapedWorktree): string {\n const branch = entry.branch !== '' ? ` [${entry.branch}]` : ' [detached]';\n // The restore command is printed inline for the same reason the branch half prints the archive\n // tag: it is the one thing that makes this reversible without going and digging in a log.\n const restore = `\\n restore: ${this.reaper.restoreCommand(entry)}`;\n // A directory that went while its branch survived is a real half-state and must not read as done.\n const partial = entry.branch !== '' && !entry.branchDeleted\n ? `\\n ⚠️ the branch '${entry.branch}' was NOT deleted — git refused it`\n : '';\n return ` ✓ ${entry.path}${branch} — ${entry.reason}${restore}${partial}\\n`;\n }\n\n /** The spared worktrees, with WHY — including the ones nobody will ever be asked about. */\n sparedBlock(verdicts: DeletableWorktree[], removed: DeletableWorktree[]): string {\n const gone = new Set(removed.map((tree: DeletableWorktree): string => tree.path));\n const spared = verdicts.filter(\n (tree: DeletableWorktree): boolean => !tree.deletable && !gone.has(tree.path)\n && this.isMechanical(tree.classification));\n if (spared.length === 0) return '';\n let out = '\\nWorktrees deliberately left alone:\\n';\n for (const tree of spared) out += ` · ${tree.path} — ${tree.reason}\\n`;\n return out;\n }\n\n private isMechanical(classification: string): boolean {\n return classification === CLASSIFICATION_LOCKED\n || classification === CLASSIFICATION_CURRENT\n || classification === CLASSIFICATION_DETACHED\n || classification === CLASSIFICATION_PRUNABLE;\n }\n\n // The table a human answers: path, branch, and the same reason the branch prompt would show, since\n // the verdict IS the branch's verdict.\n promptBlock(promptable: DeletableWorktree[]): string {\n let out = '\\n' + SEP\n + `🤔 ${String(promptable.length)} worktree(s) are probably dead — your call\\n` + SEP + '\\n'\n + 'Removing one deletes its DIRECTORY and its branch. The branch is archived as a tag first,\\n'\n + 'so both come back with one `git worktree add -b …` — but uncommitted or untracked files in\\n'\n + 'that directory are NOT archived, and git will refuse the removal if any exist.\\n\\n';\n for (let i = 0; i < promptable.length; i += 1) {\n const tree = promptable[i];\n out += ` [${String(i + 1)}] ${tree.path}\\n [${tree.branch}] — ${tree.reason}\\n`;\n }\n return out;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"worktree-cleanup.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/worktree-cleanup.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n MutationVerb,\n DeletableWorktree,\n MergedBranchesService,\n ReapedWorktree,\n WorktreeReapResult,\n WorktreeReaper,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n CLASSIFICATION_PRUNABLE,\n PROMPTABLE_CLASSIFICATIONS,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * The WORKTREE half of `wp-cleanup` — the verdicts, the reap and the human-facing text.\n *\n * Split out of CleanupCommand rather than bolted onto it because the two halves answer different\n * questions: a branch is a ref, a worktree is a DIRECTORY OF FILES, and the second one needs its own\n * report (paths, not just names), its own restore command (`git worktree add`, not `git checkout -b`)\n * and its own spared vocabulary (locked, detached, \"you are standing in it\"). Keeping them in one class\n * meant every string had to hedge about which kind of thing it was talking about.\n *\n * WHY worktrees are reaped BEFORE branches in wp-cleanup: a worktree HOLDS its branch, so that branch\n * is spared as `in-use` with \"remove that worktree before deleting the branch\". Reaping the worktree\n * first is what makes the branch reapable — the reap takes the branch with it, and the branch pass that\n * follows recomputes its verdicts against the post-removal truth. Run the other way round, every\n * worktree-held branch survives forever, which is exactly the deadlock this change exists to break.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class WorktreeCleanupSection {\n constructor(\n private readonly mergedBranches: MergedBranchesService,\n private readonly reaper: WorktreeReaper,\n ) {}\n\n /**\n * FRESH verdicts — never the cache on disk. Same rule the branch half follows: the cached file is\n * deliberately allowed to go stale, which is fine for BLOCKING a `git worktree add` and never fine\n * for removing a directory, since the tree may have gained uncommitted work since it was written.\n */\n verdicts(repoRoot: string): DeletableWorktree[] {\n return this.mergedBranches.computeMergedBranches(repoRoot).worktrees;\n }\n\n provablyDead(verdicts: DeletableWorktree[]): DeletableWorktree[] {\n return verdicts.filter((tree: DeletableWorktree): boolean => tree.deletable);\n }\n\n /**\n * The spared worktrees a human can meaningfully rule on, grouped most-safe first — the same\n * classification order the branch prompt uses, because it is literally the same verdict on the\n * branch the worktree holds.\n *\n * Deliberately excluded: LOCKED (a lock is standing and we cannot say whose), CURRENT (removing your own cwd\n * is not a thing to offer), DETACHED (no branch, so nothing to archive and nothing to judge) and\n * PRUNABLE (already provably dead — it is in the auto-reap list, not this one).\n */\n promptable(verdicts: DeletableWorktree[]): DeletableWorktree[] {\n const out: DeletableWorktree[] = [];\n for (const classification of PROMPTABLE_CLASSIFICATIONS) {\n for (const tree of verdicts) {\n if (!tree.deletable && tree.classification === classification) out.push(tree);\n }\n }\n return out;\n }\n\n reap(\n repoRoot: string,\n verb: MutationVerb,\n targets: DeletableWorktree[],\n retention: string,\n ): WorktreeReapResult {\n return this.reaper.reapWorktrees(repoRoot, process.cwd(), verb, targets, retention);\n }\n\n report(result: WorktreeReapResult): string {\n if (result.reaped.length === 0 && result.failed.length === 0) return '';\n\n let out = '\\n' + SEP + `🌲 Removed ${String(result.reaped.length)} dead worktree(s)\\n` + SEP + '\\n';\n for (const entry of result.reaped) out += this.reapedLine(entry);\n\n if (result.failed.length > 0) {\n out += `\\n⚠️ ${String(result.failed.length)} worktree(s) could not be removed:\\n`;\n for (const entry of result.failed) out += ` ✗ ${entry.path} — ${entry.error}\\n`;\n }\n // Printed on success too: removing a worktree deletes real files, and a human who cannot see\n // how to undo that has to take it on trust — which is precisely what nobody should have to do.\n out += '\\nEvery removal is logged in .webpieces/logs/branch-mutations.log (phase REAP_WORKTREE)\\n'\n + 'with the `recover=` command that brings back both the directory and its branch.\\n';\n return out;\n }\n\n private reapedLine(entry: ReapedWorktree): string {\n const branch = entry.branch !== '' ? ` [${entry.branch}]` : ' [detached]';\n // The restore command is printed inline for the same reason the branch half prints the archive\n // tag: it is the one thing that makes this reversible without going and digging in a log.\n const restore = `\\n restore: ${this.reaper.restoreCommand(entry)}`;\n // A directory that went while its branch survived is a real half-state and must not read as done.\n const partial = entry.branch !== '' && !entry.branchDeleted\n ? `\\n ⚠️ the branch '${entry.branch}' was NOT deleted — git refused it`\n : '';\n return ` ✓ ${entry.path}${branch} — ${entry.reason}${restore}${partial}\\n`;\n }\n\n /** The spared worktrees, with WHY — including the ones nobody will ever be asked about. */\n sparedBlock(verdicts: DeletableWorktree[], removed: DeletableWorktree[]): string {\n const gone = new Set(removed.map((tree: DeletableWorktree): string => tree.path));\n const spared = verdicts.filter(\n (tree: DeletableWorktree): boolean => !tree.deletable && !gone.has(tree.path)\n && this.isMechanical(tree.classification));\n if (spared.length === 0) return '';\n let out = '\\nWorktrees deliberately left alone:\\n';\n for (const tree of spared) out += ` · ${tree.path} — ${tree.reason}\\n`;\n return out;\n }\n\n private isMechanical(classification: string): boolean {\n return classification === CLASSIFICATION_LOCKED\n || classification === CLASSIFICATION_CURRENT\n || classification === CLASSIFICATION_DETACHED\n || classification === CLASSIFICATION_PRUNABLE;\n }\n\n // The table a human answers: path, branch, and the same reason the branch prompt would show, since\n // the verdict IS the branch's verdict.\n promptBlock(promptable: DeletableWorktree[]): string {\n let out = '\\n' + SEP\n + `🤔 ${String(promptable.length)} worktree(s) are probably dead — your call\\n` + SEP + '\\n'\n + 'Removing one deletes its DIRECTORY and its branch. The branch is archived as a tag first,\\n'\n + 'so both come back with one `git worktree add -b …` — but uncommitted or untracked files in\\n'\n + 'that directory are NOT archived, and git will refuse the removal if any exist.\\n\\n';\n for (let i = 0; i < promptable.length; i += 1) {\n const tree = promptable[i];\n out += ` [${String(i + 1)}] ${tree.path}\\n [${tree.branch}] — ${tree.reason}\\n`;\n }\n return out;\n }\n}\n"]}
|