@dieulc/workflow 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -2
- package/commit-policy.ts +358 -0
- package/index.ts +2258 -133
- package/package.json +4 -2
- package/permissions.ts +98 -0
- package/profiles.ts +617 -0
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dieulc/workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"keywords": ["pi-package", "workflow", "plan-mode", "review-mode", "questionnaire", "rewind"],
|
|
7
7
|
"description": "Plan↔Build mode with an independent Review Mode reviewer, questionnaire, subagent explore, todos and shadow-checkpoint rewind for Pi.",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "node --test utils.todo.test.ts permissions.test.ts roles.test.ts review.test.ts review-ui.test.ts",
|
|
10
|
+
"test": "node --test utils.todo.test.ts permissions.test.ts roles.test.ts profiles.test.ts review.test.ts review-ui.test.ts commit-policy.test.ts",
|
|
11
11
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"index.ts",
|
|
27
27
|
"utils.ts",
|
|
28
28
|
"checkpoint.ts",
|
|
29
|
+
"commit-policy.ts",
|
|
29
30
|
"roles.ts",
|
|
31
|
+
"profiles.ts",
|
|
30
32
|
"permissions.ts",
|
|
31
33
|
"review.ts",
|
|
32
34
|
"review-providers.ts",
|
package/permissions.ts
CHANGED
|
@@ -824,6 +824,104 @@ function classifyGit(s: string): CommandClass {
|
|
|
824
824
|
return GIT_READ_ONLY_SUBCOMMANDS.has(sub) ? "read-only" : "full";
|
|
825
825
|
}
|
|
826
826
|
|
|
827
|
+
// ── Commit-policy classification ─────────────────────────────────
|
|
828
|
+
|
|
829
|
+
/** Git subcommands the commit policy gates (`workflow.commitPolicy`). */
|
|
830
|
+
export type CommitPolicyHit = "add" | "commit" | "push";
|
|
831
|
+
|
|
832
|
+
const COMMIT_POLICY_SUBCOMMANDS: ReadonlySet<string> = new Set([
|
|
833
|
+
"add",
|
|
834
|
+
"commit",
|
|
835
|
+
"push",
|
|
836
|
+
]);
|
|
837
|
+
|
|
838
|
+
/** Global `git` options that take a separate value (`git -C path status`). */
|
|
839
|
+
const GIT_GLOBAL_VALUE_OPTIONS: ReadonlySet<string> = new Set([
|
|
840
|
+
"-C",
|
|
841
|
+
"--git-dir",
|
|
842
|
+
"--work-tree",
|
|
843
|
+
"--namespace",
|
|
844
|
+
"--super-prefix",
|
|
845
|
+
"--exec-path",
|
|
846
|
+
]);
|
|
847
|
+
|
|
848
|
+
/** Global `git` flags that take no value (`git --no-pager log`). */
|
|
849
|
+
const GIT_GLOBAL_FLAG_OPTIONS: ReadonlySet<string> = new Set([
|
|
850
|
+
"--no-pager",
|
|
851
|
+
"--paginate",
|
|
852
|
+
"--no-replace-objects",
|
|
853
|
+
"--bare",
|
|
854
|
+
"--no-optional-locks",
|
|
855
|
+
"--literal-pathspecs",
|
|
856
|
+
"--html-path",
|
|
857
|
+
"--man-path",
|
|
858
|
+
"--info-path",
|
|
859
|
+
"--version",
|
|
860
|
+
]);
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Extract the subcommand a `git …` invocation resolves to, skipping global
|
|
864
|
+
* options. `null` when no subcommand is found. Mirrors classifyGit's
|
|
865
|
+
* option-skipping, but deliberately parses *through* `-c key=value` pairs:
|
|
866
|
+
* a config pair cannot turn a read-only subcommand into a commit, and the
|
|
867
|
+
* policy classifier must see the real subcommand (`git -c x=y commit`).
|
|
868
|
+
*/
|
|
869
|
+
function gitSubcommandAt(words: string[], gitIndex: number): string | null {
|
|
870
|
+
let i = gitIndex + 1;
|
|
871
|
+
while (i < words.length) {
|
|
872
|
+
const w = unquote(words[i]);
|
|
873
|
+
if (!w) {
|
|
874
|
+
i += 1;
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (w === "-c") {
|
|
878
|
+
i += 2; // -c key=value pair
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
if (w.startsWith("-c") && w.includes("=")) {
|
|
882
|
+
i += 1; // fused -ckey=value (defensive; git wants a space)
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (w.startsWith("--") && w.includes("=")) {
|
|
886
|
+
i += 1; // --git-dir=/x fused form
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (GIT_GLOBAL_VALUE_OPTIONS.has(w)) {
|
|
890
|
+
i += 2;
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (GIT_GLOBAL_FLAG_OPTIONS.has(w)) {
|
|
894
|
+
i += 1;
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
return w.toLowerCase();
|
|
898
|
+
}
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Commit-policy classifier: does this command invoke `git add`, `git commit`
|
|
904
|
+
* or `git push` — in any segment of a compound command, including wrapper
|
|
905
|
+
* forms (`env git commit`, `nice -n 5 git add`) and global-option forms
|
|
906
|
+
* (`git -C path add`, `git.exe push`)? Returns the first matched subcommand,
|
|
907
|
+
* or null. Fail-closed by design: false positives (an unquoted
|
|
908
|
+
* `echo git commit`) are acceptable; false negatives are not.
|
|
909
|
+
*/
|
|
910
|
+
export function gitCommitPolicyHit(command: string): CommitPolicyHit | null {
|
|
911
|
+
if (typeof command !== "string" || !command.trim()) return null;
|
|
912
|
+
for (const segment of splitCommandSegments(command)) {
|
|
913
|
+
const words = wordsOf(segment);
|
|
914
|
+
for (let g = 0; g < words.length; g++) {
|
|
915
|
+
if (normalizeBinary(words[g]) !== "git") continue;
|
|
916
|
+
const sub = gitSubcommandAt(words, g);
|
|
917
|
+
if (sub && COMMIT_POLICY_SUBCOMMANDS.has(sub)) {
|
|
918
|
+
return sub as CommitPolicyHit;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
|
|
827
925
|
function classifyDocker(s: string): CommandClass {
|
|
828
926
|
const sub = wordAt(s, 1)?.toLowerCase();
|
|
829
927
|
if (!sub) return "full";
|