@cad0p/pi-steering 0.1.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/CHANGELOG.md +36 -0
- package/LICENSE +29 -0
- package/README.md +920 -0
- package/dist/__test-helpers__.d.ts +95 -0
- package/dist/__test-helpers__.d.ts.map +1 -0
- package/dist/__test-helpers__.js +174 -0
- package/dist/__test-helpers__.js.map +1 -0
- package/dist/bin/pi-steering.d.ts +9 -0
- package/dist/bin/pi-steering.d.ts.map +1 -0
- package/dist/bin/pi-steering.js +506 -0
- package/dist/bin/pi-steering.js.map +1 -0
- package/dist/compat.d.ts +50 -0
- package/dist/compat.d.ts.map +1 -0
- package/dist/compat.js +180 -0
- package/dist/compat.js.map +1 -0
- package/dist/defaults.d.ts +61 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +106 -0
- package/dist/defaults.js.map +1 -0
- package/dist/define-config.d.ts +235 -0
- package/dist/define-config.d.ts.map +1 -0
- package/dist/define-config.js +118 -0
- package/dist/define-config.js.map +1 -0
- package/dist/define-predicate.d.ts +49 -0
- package/dist/define-predicate.d.ts.map +1 -0
- package/dist/define-predicate.js +35 -0
- package/dist/define-predicate.js.map +1 -0
- package/dist/evaluator-internals/context.d.ts +186 -0
- package/dist/evaluator-internals/context.d.ts.map +1 -0
- package/dist/evaluator-internals/context.js +219 -0
- package/dist/evaluator-internals/context.js.map +1 -0
- package/dist/evaluator-internals/override.d.ts +19 -0
- package/dist/evaluator-internals/override.d.ts.map +1 -0
- package/dist/evaluator-internals/override.js +79 -0
- package/dist/evaluator-internals/override.js.map +1 -0
- package/dist/evaluator-internals/predicates.d.ts +152 -0
- package/dist/evaluator-internals/predicates.d.ts.map +1 -0
- package/dist/evaluator-internals/predicates.js +849 -0
- package/dist/evaluator-internals/predicates.js.map +1 -0
- package/dist/evaluator-internals/speculative-synthesis.d.ts +102 -0
- package/dist/evaluator-internals/speculative-synthesis.d.ts.map +1 -0
- package/dist/evaluator-internals/speculative-synthesis.js +101 -0
- package/dist/evaluator-internals/speculative-synthesis.js.map +1 -0
- package/dist/evaluator.d.ts +101 -0
- package/dist/evaluator.d.ts.map +1 -0
- package/dist/evaluator.js +714 -0
- package/dist/evaluator.js.map +1 -0
- package/dist/helpers/walker-unknown-cwd-reason.d.ts +68 -0
- package/dist/helpers/walker-unknown-cwd-reason.d.ts.map +1 -0
- package/dist/helpers/walker-unknown-cwd-reason.js +73 -0
- package/dist/helpers/walker-unknown-cwd-reason.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/drop-unused-observers.d.ts +43 -0
- package/dist/internal/drop-unused-observers.d.ts.map +1 -0
- package/dist/internal/drop-unused-observers.js +50 -0
- package/dist/internal/drop-unused-observers.js.map +1 -0
- package/dist/internal/finalize-plugin-state.d.ts +22 -0
- package/dist/internal/finalize-plugin-state.d.ts.map +1 -0
- package/dist/internal/finalize-plugin-state.js +14 -0
- package/dist/internal/finalize-plugin-state.js.map +1 -0
- package/dist/internal/merge-observers.d.ts +30 -0
- package/dist/internal/merge-observers.d.ts.map +1 -0
- package/dist/internal/merge-observers.js +27 -0
- package/dist/internal/merge-observers.js.map +1 -0
- package/dist/internal/pattern-utils.d.ts +33 -0
- package/dist/internal/pattern-utils.d.ts.map +1 -0
- package/dist/internal/pattern-utils.js +20 -0
- package/dist/internal/pattern-utils.js.map +1 -0
- package/dist/internal/ref-text.d.ts +11 -0
- package/dist/internal/ref-text.d.ts.map +1 -0
- package/dist/internal/ref-text.js +15 -0
- package/dist/internal/ref-text.js.map +1 -0
- package/dist/internal/session-runtime.d.ts +44 -0
- package/dist/internal/session-runtime.d.ts.map +1 -0
- package/dist/internal/session-runtime.js +143 -0
- package/dist/internal/session-runtime.js.map +1 -0
- package/dist/internal/watch-matcher.d.ts +56 -0
- package/dist/internal/watch-matcher.d.ts.map +1 -0
- package/dist/internal/watch-matcher.js +179 -0
- package/dist/internal/watch-matcher.js.map +1 -0
- package/dist/loader.d.ts +97 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +527 -0
- package/dist/loader.js.map +1 -0
- package/dist/observer-dispatcher.d.ts +69 -0
- package/dist/observer-dispatcher.d.ts.map +1 -0
- package/dist/observer-dispatcher.js +184 -0
- package/dist/observer-dispatcher.js.map +1 -0
- package/dist/plugin-merger.d.ts +175 -0
- package/dist/plugin-merger.d.ts.map +1 -0
- package/dist/plugin-merger.js +450 -0
- package/dist/plugin-merger.js.map +1 -0
- package/dist/plugins/git/branch-tracker.d.ts +85 -0
- package/dist/plugins/git/branch-tracker.d.ts.map +1 -0
- package/dist/plugins/git/branch-tracker.js +181 -0
- package/dist/plugins/git/branch-tracker.js.map +1 -0
- package/dist/plugins/git/cwd-extensions.d.ts +10 -0
- package/dist/plugins/git/cwd-extensions.d.ts.map +1 -0
- package/dist/plugins/git/cwd-extensions.js +155 -0
- package/dist/plugins/git/cwd-extensions.js.map +1 -0
- package/dist/plugins/git/git-ops.d.ts +89 -0
- package/dist/plugins/git/git-ops.d.ts.map +1 -0
- package/dist/plugins/git/git-ops.js +107 -0
- package/dist/plugins/git/git-ops.js.map +1 -0
- package/dist/plugins/git/index.d.ts +225 -0
- package/dist/plugins/git/index.d.ts.map +1 -0
- package/dist/plugins/git/index.js +52 -0
- package/dist/plugins/git/index.js.map +1 -0
- package/dist/plugins/git/predicates.d.ts +312 -0
- package/dist/plugins/git/predicates.d.ts.map +1 -0
- package/dist/plugins/git/predicates.js +580 -0
- package/dist/plugins/git/predicates.js.map +1 -0
- package/dist/plugins/git/rules.d.ts +209 -0
- package/dist/plugins/git/rules.d.ts.map +1 -0
- package/dist/plugins/git/rules.js +267 -0
- package/dist/plugins/git/rules.js.map +1 -0
- package/dist/schema.d.ts +1799 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +4 -0
- package/dist/schema.js.map +1 -0
- package/dist/testing/index.d.ts +621 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +747 -0
- package/dist/testing/index.js.map +1 -0
- package/examples/README.md +83 -0
- package/examples/combined-git-discipline/README.md +52 -0
- package/examples/combined-git-discipline/node_modules/.bin/pi-steering +43 -0
- package/examples/combined-git-discipline/package.json +18 -0
- package/examples/combined-git-discipline/steering.json +27 -0
- package/examples/combined-git-discipline/steering.test.ts +42 -0
- package/examples/combined-git-discipline/steering.ts +64 -0
- package/examples/combined-git-discipline/tsconfig.json +7 -0
- package/examples/draft-prs-only/README.md +42 -0
- package/examples/draft-prs-only/node_modules/.bin/pi-steering +43 -0
- package/examples/draft-prs-only/package.json +18 -0
- package/examples/draft-prs-only/steering.json +12 -0
- package/examples/draft-prs-only/steering.test.ts +48 -0
- package/examples/draft-prs-only/steering.ts +36 -0
- package/examples/draft-prs-only/tsconfig.json +7 -0
- package/examples/dynamic-reason-runtime-cwd/README.md +148 -0
- package/examples/dynamic-reason-runtime-cwd/node_modules/.bin/pi-steering +43 -0
- package/examples/dynamic-reason-runtime-cwd/package.json +18 -0
- package/examples/dynamic-reason-runtime-cwd/steering.test.ts +130 -0
- package/examples/dynamic-reason-runtime-cwd/steering.ts +82 -0
- package/examples/dynamic-reason-runtime-cwd/tsconfig.json +7 -0
- package/examples/force-push-strict/README.md +75 -0
- package/examples/force-push-strict/node_modules/.bin/pi-steering +43 -0
- package/examples/force-push-strict/package.json +18 -0
- package/examples/force-push-strict/steering.json +12 -0
- package/examples/force-push-strict/steering.test.ts +51 -0
- package/examples/force-push-strict/steering.ts +54 -0
- package/examples/force-push-strict/tsconfig.json +7 -0
- package/examples/no-amend/README.md +52 -0
- package/examples/no-amend/node_modules/.bin/pi-steering +43 -0
- package/examples/no-amend/package.json +18 -0
- package/examples/no-amend/steering.cwd-scoped.json +12 -0
- package/examples/no-amend/steering.json +11 -0
- package/examples/no-amend/steering.test.ts +49 -0
- package/examples/no-amend/steering.ts +38 -0
- package/examples/no-amend/tsconfig.json +7 -0
- package/examples/work-item-plugin/README.md +103 -0
- package/examples/work-item-plugin/node_modules/.bin/pi-steering +43 -0
- package/examples/work-item-plugin/package.json +19 -0
- package/examples/work-item-plugin/src/index.test.ts +168 -0
- package/examples/work-item-plugin/src/index.ts +122 -0
- package/examples/work-item-plugin/src/observers/npm-test-tracker.test.ts +95 -0
- package/examples/work-item-plugin/src/observers/npm-test-tracker.ts +88 -0
- package/examples/work-item-plugin/src/observers/retest-required-tracker.test.ts +81 -0
- package/examples/work-item-plugin/src/observers/retest-required-tracker.ts +81 -0
- package/examples/work-item-plugin/src/predicates/work-item-format.test.ts +157 -0
- package/examples/work-item-plugin/src/predicates/work-item-format.ts +99 -0
- package/examples/work-item-plugin/src/rules/commit-description-check.test.ts +107 -0
- package/examples/work-item-plugin/src/rules/commit-description-check.ts +83 -0
- package/examples/work-item-plugin/src/rules/commit-requires-work-item.test.ts +79 -0
- package/examples/work-item-plugin/src/rules/commit-requires-work-item.ts +56 -0
- package/examples/work-item-plugin/src/rules/push-requires-tests.test.ts +245 -0
- package/examples/work-item-plugin/src/rules/push-requires-tests.ts +62 -0
- package/examples/work-item-plugin/tsconfig.json +8 -0
- package/package.json +88 -0
- package/skills/steering-authoring/SKILL.md +102 -0
- package/src/INVARIANTS.md +116 -0
- package/src/__test-helpers__.ts +243 -0
- package/src/bin/pi-steering.test.ts +920 -0
- package/src/bin/pi-steering.ts +584 -0
- package/src/compat.test.ts +431 -0
- package/src/compat.ts +282 -0
- package/src/defaults.test.ts +585 -0
- package/src/defaults.ts +148 -0
- package/src/define-config.test.ts +888 -0
- package/src/define-config.ts +375 -0
- package/src/define-predicate.test.ts +70 -0
- package/src/define-predicate.ts +57 -0
- package/src/evaluator-internals/context.ts +290 -0
- package/src/evaluator-internals/override.test.ts +99 -0
- package/src/evaluator-internals/override.ts +83 -0
- package/src/evaluator-internals/predicates.ts +1037 -0
- package/src/evaluator-internals/speculative-synthesis.test.ts +411 -0
- package/src/evaluator-internals/speculative-synthesis.ts +199 -0
- package/src/evaluator.test.ts +6296 -0
- package/src/evaluator.ts +1106 -0
- package/src/factory-time-load.test.ts +630 -0
- package/src/helpers/walker-unknown-cwd-reason.test.ts +73 -0
- package/src/helpers/walker-unknown-cwd-reason.ts +83 -0
- package/src/index.test.ts +836 -0
- package/src/index.ts +229 -0
- package/src/internal/drop-unused-observers.test.ts +141 -0
- package/src/internal/drop-unused-observers.ts +81 -0
- package/src/internal/finalize-plugin-state.ts +43 -0
- package/src/internal/merge-observers.ts +51 -0
- package/src/internal/pattern-utils.test.ts +52 -0
- package/src/internal/pattern-utils.ts +38 -0
- package/src/internal/ref-text.test.ts +54 -0
- package/src/internal/ref-text.ts +17 -0
- package/src/internal/session-runtime.test.ts +629 -0
- package/src/internal/session-runtime.ts +201 -0
- package/src/internal/watch-matcher.test.ts +326 -0
- package/src/internal/watch-matcher.ts +200 -0
- package/src/jsdoc-propagation.test.ts +277 -0
- package/src/loader.test.ts +1098 -0
- package/src/loader.ts +581 -0
- package/src/not-block-onunknown.test.ts +554 -0
- package/src/observer-dispatcher.test.ts +939 -0
- package/src/observer-dispatcher.ts +290 -0
- package/src/per-predicate-typing.test.ts +361 -0
- package/src/plugin-merger.test.ts +790 -0
- package/src/plugin-merger.ts +598 -0
- package/src/plugins/git/README.md +485 -0
- package/src/plugins/git/branch-tracker.test.ts +281 -0
- package/src/plugins/git/branch-tracker.ts +187 -0
- package/src/plugins/git/cwd-extensions.test.ts +230 -0
- package/src/plugins/git/cwd-extensions.ts +160 -0
- package/src/plugins/git/git-ops.test.ts +397 -0
- package/src/plugins/git/git-ops.ts +173 -0
- package/src/plugins/git/index.ts +253 -0
- package/src/plugins/git/integration.test.ts +705 -0
- package/src/plugins/git/predicates.test.ts +1401 -0
- package/src/plugins/git/predicates.ts +693 -0
- package/src/plugins/git/rules.test.ts +1001 -0
- package/src/plugins/git/rules.ts +307 -0
- package/src/schema.test.ts +259 -0
- package/src/schema.ts +1963 -0
- package/src/testing/index.test.ts +1696 -0
- package/src/testing/index.ts +1472 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Part of the pi-steering work-item-plugin example.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Work-item example plugin — canonical reference for pi-steering
|
|
6
|
+
* plugin authors (ADR §15).
|
|
7
|
+
*
|
|
8
|
+
* What this plugin demonstrates, file-by-file:
|
|
9
|
+
*
|
|
10
|
+
* - `predicates/work-item-format.ts`
|
|
11
|
+
* - `definePredicate<T>` for typed predicate-arg variance.
|
|
12
|
+
* - Structured arg access via `input.args` (quote-aware).
|
|
13
|
+
* - `observers/npm-test-tracker.ts`
|
|
14
|
+
* - ADR §14 encapsulation convention: file exports the
|
|
15
|
+
* `<EVENT>_EVENT` constant AND a `mark<Event>(ctx)` helper;
|
|
16
|
+
* observer uses the helper.
|
|
17
|
+
* - `writes` declaration threading through for
|
|
18
|
+
* `defineConfig`'s compile-time type checking.
|
|
19
|
+
* - `observers/retest-required-tracker.ts`
|
|
20
|
+
* - Invalidation-sentinel pattern: observer writes
|
|
21
|
+
* `RETEST_REQUIRED_EVENT` on `git pull`, which stale-s
|
|
22
|
+
* prior `TEST_PASSED_EVENT` entries via `happened.since`.
|
|
23
|
+
* - `rules/commit-requires-work-item.ts`
|
|
24
|
+
* - Plugin-registered predicate consumption via `when.<key>`.
|
|
25
|
+
* - `not:` inversion in a {@link TopLevelWhenClause}.
|
|
26
|
+
* - `rules/push-requires-tests.ts`
|
|
27
|
+
* - `when.happened: { in: "agent_loop" }` gating.
|
|
28
|
+
* - Observer → rule coupling via the shared EVENT constants.
|
|
29
|
+
* - Temporal invalidation via `happened.since`.
|
|
30
|
+
* - Chain-aware speculative allow for `npm test && git push`.
|
|
31
|
+
* - `rules/commit-description-check.ts`
|
|
32
|
+
* - Self-marking rules with `onFire`.
|
|
33
|
+
* - Constant + helper co-located with the rule when no
|
|
34
|
+
* observer corresponds (ADR §14).
|
|
35
|
+
*
|
|
36
|
+
* Copy-adapt this layout. A real plugin likely ships more rules and
|
|
37
|
+
* possibly a tracker too — see `src/plugins/git/`
|
|
38
|
+
* for the tracker + tracker-extension pattern.
|
|
39
|
+
*
|
|
40
|
+
* ## Consuming this plugin
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { defineConfig } from "@cad0p/pi-steering";
|
|
44
|
+
* import workItemPlugin from "@examples/work-item-plugin";
|
|
45
|
+
*
|
|
46
|
+
* export default defineConfig({
|
|
47
|
+
* plugins: [workItemPlugin],
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import type { Plugin, PredicateShape } from "@cad0p/pi-steering";
|
|
53
|
+
import {
|
|
54
|
+
npmTestTracker,
|
|
55
|
+
TEST_PASSED_EVENT,
|
|
56
|
+
} from "./observers/npm-test-tracker.ts";
|
|
57
|
+
import {
|
|
58
|
+
RETEST_REQUIRED_EVENT,
|
|
59
|
+
retestRequiredTracker,
|
|
60
|
+
} from "./observers/retest-required-tracker.ts";
|
|
61
|
+
import type { WorkItemFormatArgs } from "./predicates/work-item-format.ts";
|
|
62
|
+
import { workItemFormat } from "./predicates/work-item-format.ts";
|
|
63
|
+
import {
|
|
64
|
+
commitDescriptionCheck,
|
|
65
|
+
DESCRIPTION_REVIEWED_EVENT,
|
|
66
|
+
} from "./rules/commit-description-check.ts";
|
|
67
|
+
import { commitRequiresWorkItem } from "./rules/commit-requires-work-item.ts";
|
|
68
|
+
import { pushRequiresTests } from "./rules/push-requires-tests.ts";
|
|
69
|
+
|
|
70
|
+
declare global {
|
|
71
|
+
/**
|
|
72
|
+
* Plugin author registers `workItemFormat:` so the engine's mapped
|
|
73
|
+
* type ({@link TopLevelWhenClause}) accepts it as a leaf-level key
|
|
74
|
+
* with a typed argument shape. Without this augmentation, rules in
|
|
75
|
+
* this plugin (or any consumer's rules) referencing `workItemFormat:`
|
|
76
|
+
* would fail typecheck against the strict registry-driven type.
|
|
77
|
+
*
|
|
78
|
+
* @see PredicateShape, PiSteeringPredicates in pi-steering's schema.
|
|
79
|
+
*/
|
|
80
|
+
interface PiSteeringPredicates {
|
|
81
|
+
workItemFormat: PredicateShape<WorkItemFormatArgs>;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Re-export the type constants so consumers (e.g. another plugin or
|
|
86
|
+
// a user's custom rule) can gate on the same events without
|
|
87
|
+
// rediscovering the literal strings.
|
|
88
|
+
export { DESCRIPTION_REVIEWED_EVENT, RETEST_REQUIRED_EVENT, TEST_PASSED_EVENT };
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The plugin. `as const satisfies Plugin` preserves the literal
|
|
92
|
+
* `name: "work-item"` and the `writes` tuples from rules/observers
|
|
93
|
+
* so `defineConfig` can cross-reference `when.happened.event` usages
|
|
94
|
+
* against this plugin's declared writes. See the ADR §7 footgun
|
|
95
|
+
* about bare `: Plugin` annotations.
|
|
96
|
+
*/
|
|
97
|
+
const workItemPlugin = {
|
|
98
|
+
name: "work-item",
|
|
99
|
+
predicates: { workItemFormat },
|
|
100
|
+
rules: [commitRequiresWorkItem, pushRequiresTests, commitDescriptionCheck],
|
|
101
|
+
observers: [npmTestTracker, retestRequiredTracker],
|
|
102
|
+
} as const satisfies Plugin;
|
|
103
|
+
|
|
104
|
+
export default workItemPlugin;
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
markTestPassed,
|
|
108
|
+
npmTestTracker,
|
|
109
|
+
} from "./observers/npm-test-tracker.ts";
|
|
110
|
+
export {
|
|
111
|
+
markRetestRequired,
|
|
112
|
+
retestRequiredTracker,
|
|
113
|
+
} from "./observers/retest-required-tracker.ts";
|
|
114
|
+
// Named re-exports — pick-your-piece imports for authors who want
|
|
115
|
+
// just one rule or the predicate.
|
|
116
|
+
export { workItemFormat } from "./predicates/work-item-format.ts";
|
|
117
|
+
export {
|
|
118
|
+
commitDescriptionCheck,
|
|
119
|
+
markDescriptionReviewed,
|
|
120
|
+
} from "./rules/commit-description-check.ts";
|
|
121
|
+
export { commitRequiresWorkItem } from "./rules/commit-requires-work-item.ts";
|
|
122
|
+
export { pushRequiresTests } from "./rules/push-requires-tests.ts";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Part of the pi-steering work-item-plugin example.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unit tests for the `npm-test-tracker` observer.
|
|
6
|
+
*
|
|
7
|
+
* Exercises the watch-filter + write-on-match contract via
|
|
8
|
+
* `testObserver`, which captures `appendEntry` calls for inspection.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { describe, it } from "node:test";
|
|
13
|
+
import { testObserver } from "@cad0p/pi-steering/testing";
|
|
14
|
+
import { npmTestTracker, TEST_PASSED_EVENT } from "./npm-test-tracker.ts";
|
|
15
|
+
|
|
16
|
+
describe("npm-test-tracker observer", () => {
|
|
17
|
+
it("records a TEST_PASSED_EVENT entry on successful `npm test`", async () => {
|
|
18
|
+
const { entries, watchMatched } = await testObserver(npmTestTracker, {
|
|
19
|
+
toolName: "bash",
|
|
20
|
+
input: { command: "npm test" },
|
|
21
|
+
output: {},
|
|
22
|
+
exitCode: 0,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
assert.equal(watchMatched, true);
|
|
26
|
+
assert.equal(entries.length, 1);
|
|
27
|
+
assert.equal(entries[0]?.customType, TEST_PASSED_EVENT);
|
|
28
|
+
assert.deepEqual(entries[0]?.data, {
|
|
29
|
+
command: "npm test",
|
|
30
|
+
// The dispatcher auto-tags plain-object payloads with
|
|
31
|
+
// `_agentLoopIndex` — default mockObserverContext has index
|
|
32
|
+
// 0.
|
|
33
|
+
_agentLoopIndex: 0,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("does NOT fire on a failed `npm test` (exitCode non-zero)", async () => {
|
|
38
|
+
const { entries, watchMatched } = await testObserver(npmTestTracker, {
|
|
39
|
+
toolName: "bash",
|
|
40
|
+
input: { command: "npm test" },
|
|
41
|
+
output: {},
|
|
42
|
+
exitCode: 1,
|
|
43
|
+
});
|
|
44
|
+
assert.equal(watchMatched, false);
|
|
45
|
+
assert.equal(entries.length, 0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("does NOT fire on an unrelated command", async () => {
|
|
49
|
+
const { entries, watchMatched } = await testObserver(npmTestTracker, {
|
|
50
|
+
toolName: "bash",
|
|
51
|
+
input: { command: "npm install" },
|
|
52
|
+
output: {},
|
|
53
|
+
exitCode: 0,
|
|
54
|
+
});
|
|
55
|
+
assert.equal(watchMatched, false);
|
|
56
|
+
assert.equal(entries.length, 0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("fires on `npm test -- --grep foo` (watch regex unanchored to suffix)", async () => {
|
|
60
|
+
const { entries, watchMatched } = await testObserver(npmTestTracker, {
|
|
61
|
+
toolName: "bash",
|
|
62
|
+
input: { command: "npm test -- --grep foo" },
|
|
63
|
+
output: {},
|
|
64
|
+
exitCode: 0,
|
|
65
|
+
});
|
|
66
|
+
assert.equal(watchMatched, true);
|
|
67
|
+
assert.equal(entries.length, 1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("records the original command in the payload", async () => {
|
|
71
|
+
const { entries } = await testObserver(npmTestTracker, {
|
|
72
|
+
toolName: "bash",
|
|
73
|
+
input: { command: "npm test -- --reporter dot" },
|
|
74
|
+
output: {},
|
|
75
|
+
exitCode: 0,
|
|
76
|
+
});
|
|
77
|
+
const data = entries[0]?.data as { command: string };
|
|
78
|
+
assert.equal(data.command, "npm test -- --reporter dot");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("tags writes with the agent-loop index from ctx", async () => {
|
|
82
|
+
const { entries } = await testObserver(
|
|
83
|
+
npmTestTracker,
|
|
84
|
+
{
|
|
85
|
+
toolName: "bash",
|
|
86
|
+
input: { command: "npm test" },
|
|
87
|
+
output: {},
|
|
88
|
+
exitCode: 0,
|
|
89
|
+
},
|
|
90
|
+
{ agentLoopIndex: 42 },
|
|
91
|
+
);
|
|
92
|
+
const data = entries[0]?.data as { _agentLoopIndex: number };
|
|
93
|
+
assert.equal(data._agentLoopIndex, 42);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Part of the pi-steering work-item-plugin example.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `npm-test-tracker` — example observer.
|
|
6
|
+
*
|
|
7
|
+
* Watches `bash` tool_results where the command matches `/^npm test/`
|
|
8
|
+
* with `exitCode: "success"`. On match, records a `TEST_PASSED_EVENT`
|
|
9
|
+
* session entry so rules can later consult whether tests have passed
|
|
10
|
+
* this agent loop.
|
|
11
|
+
*
|
|
12
|
+
* ## What this file demonstrates (ADR §14)
|
|
13
|
+
*
|
|
14
|
+
* The canonical observer encapsulation convention — every observer
|
|
15
|
+
* file exports three things:
|
|
16
|
+
*
|
|
17
|
+
* 1. A `<EVENT>_EVENT` constant (the session-entry event type).
|
|
18
|
+
* 2. A `mark<Event>` helper that calls `ctx.appendEntry(<TYPE>)`
|
|
19
|
+
* with the right shape.
|
|
20
|
+
* 3. The observer itself, using the helper.
|
|
21
|
+
*
|
|
22
|
+
* Rules that gate on this event import `TEST_PASSED_EVENT` (not the
|
|
23
|
+
* raw string) and/or call `markTestPassed` from their own `onFire`.
|
|
24
|
+
* The raw string literal lives in exactly one place — here — and
|
|
25
|
+
* downstream typos become compile errors.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { Observer, ObserverContext, PredicateContext } from "@cad0p/pi-steering";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Session-entry type written when `npm test` succeeds. Rules gate via
|
|
32
|
+
* `when: { happened: { event: TEST_PASSED_EVENT, in: "agent_loop" } }`.
|
|
33
|
+
*/
|
|
34
|
+
export const TEST_PASSED_EVENT = "example-npm-test-passed" as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Typed shape for the payload we write. Observers that need richer
|
|
38
|
+
* state (e.g. the test command variant, time-stamp, etc.) extend this
|
|
39
|
+
* interface; keeping the write helper as the one-stop shape definition
|
|
40
|
+
* keeps rule authors reading just the observer file to understand the
|
|
41
|
+
* contract.
|
|
42
|
+
*/
|
|
43
|
+
export interface TestPassedPayload {
|
|
44
|
+
/** Which command variant passed. Handy for debugging / filtering. */
|
|
45
|
+
command: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Record a "tests passed" session entry. Callable from either an
|
|
50
|
+
* observer (`tool_result` hook) or a rule's `onFire` hook — both
|
|
51
|
+
* receive a ctx with `appendEntry`. The loose context type here keeps
|
|
52
|
+
* the helper reusable across both.
|
|
53
|
+
*/
|
|
54
|
+
export function markTestPassed(
|
|
55
|
+
ctx: ObserverContext | PredicateContext,
|
|
56
|
+
payload: TestPassedPayload = { command: "npm test" },
|
|
57
|
+
): void {
|
|
58
|
+
ctx.appendEntry<TestPassedPayload>(TEST_PASSED_EVENT, payload);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The observer itself. `as const satisfies Observer` preserves the
|
|
63
|
+
* literal `name: "npm-test-tracker"` and the `writes` tuple so
|
|
64
|
+
* `defineConfig`'s compile-time cross-reference checking works —
|
|
65
|
+
* rules referencing `happened: { event: TEST_PASSED_EVENT }` get
|
|
66
|
+
* validated against this observer's declared writes.
|
|
67
|
+
*
|
|
68
|
+
* See ADR §7 (`writes` declarations) for the authoring-pattern
|
|
69
|
+
* footgun around bare `: Observer` annotations.
|
|
70
|
+
*/
|
|
71
|
+
export const npmTestTracker = {
|
|
72
|
+
name: "npm-test-tracker",
|
|
73
|
+
writes: [TEST_PASSED_EVENT],
|
|
74
|
+
watch: {
|
|
75
|
+
toolName: "bash",
|
|
76
|
+
inputMatches: { command: /^npm\s+test\b/ },
|
|
77
|
+
exitCode: "success",
|
|
78
|
+
},
|
|
79
|
+
onResult: (event, ctx) => {
|
|
80
|
+
// Pull the command out of the raw event for the payload. The
|
|
81
|
+
// watch filter already guaranteed we're on a bash event whose
|
|
82
|
+
// `input.command` matches our regex — the cast is safe.
|
|
83
|
+
const input = event.input as { command?: string } | undefined;
|
|
84
|
+
markTestPassed(ctx, {
|
|
85
|
+
command: input?.command ?? "npm test",
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
} as const satisfies Observer;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Part of the pi-steering work-item-plugin example.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unit tests for the `retest-required-tracker` observer.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the structure of `npm-test-tracker.test.ts` — exercises
|
|
8
|
+
* the watch-filter + write-on-match contract via `testObserver`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { describe, it } from "node:test";
|
|
13
|
+
import { testObserver } from "@cad0p/pi-steering/testing";
|
|
14
|
+
import {
|
|
15
|
+
RETEST_REQUIRED_EVENT,
|
|
16
|
+
retestRequiredTracker,
|
|
17
|
+
} from "./retest-required-tracker.ts";
|
|
18
|
+
|
|
19
|
+
describe("retest-required-tracker observer", () => {
|
|
20
|
+
it("records a RETEST_REQUIRED_EVENT entry on successful `git pull`", async () => {
|
|
21
|
+
const { entries, watchMatched } = await testObserver(
|
|
22
|
+
retestRequiredTracker,
|
|
23
|
+
{
|
|
24
|
+
toolName: "bash",
|
|
25
|
+
input: { command: "git pull" },
|
|
26
|
+
output: {},
|
|
27
|
+
exitCode: 0,
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
assert.equal(watchMatched, true);
|
|
32
|
+
assert.equal(entries.length, 1);
|
|
33
|
+
assert.equal(entries[0]?.customType, RETEST_REQUIRED_EVENT);
|
|
34
|
+
assert.deepEqual(entries[0]?.data, {
|
|
35
|
+
command: "git pull",
|
|
36
|
+
_agentLoopIndex: 0,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("does NOT fire on failed `git pull`", async () => {
|
|
41
|
+
const { entries, watchMatched } = await testObserver(
|
|
42
|
+
retestRequiredTracker,
|
|
43
|
+
{
|
|
44
|
+
toolName: "bash",
|
|
45
|
+
input: { command: "git pull" },
|
|
46
|
+
output: {},
|
|
47
|
+
exitCode: 1,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
assert.equal(watchMatched, false);
|
|
51
|
+
assert.equal(entries.length, 0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("does NOT fire on unrelated commands", async () => {
|
|
55
|
+
const { entries, watchMatched } = await testObserver(
|
|
56
|
+
retestRequiredTracker,
|
|
57
|
+
{
|
|
58
|
+
toolName: "bash",
|
|
59
|
+
input: { command: "git push" },
|
|
60
|
+
output: {},
|
|
61
|
+
exitCode: 0,
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
assert.equal(watchMatched, false);
|
|
65
|
+
assert.equal(entries.length, 0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("fires on `git pull --rebase` (watch regex unanchored to suffix)", async () => {
|
|
69
|
+
const { entries, watchMatched } = await testObserver(
|
|
70
|
+
retestRequiredTracker,
|
|
71
|
+
{
|
|
72
|
+
toolName: "bash",
|
|
73
|
+
input: { command: "git pull --rebase" },
|
|
74
|
+
output: {},
|
|
75
|
+
exitCode: 0,
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
assert.equal(watchMatched, true);
|
|
79
|
+
assert.equal(entries.length, 1);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Part of the pi-steering work-item-plugin example.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `retest-required-tracker` — invalidation observer for the `since`
|
|
6
|
+
* sentinel pattern.
|
|
7
|
+
*
|
|
8
|
+
* ## What this file demonstrates (commit-4 polish, ADR §5 `since`)
|
|
9
|
+
*
|
|
10
|
+
* Observers can write "invalidator" events whose presence stale-s an
|
|
11
|
+
* earlier satisfied `happened` clause:
|
|
12
|
+
*
|
|
13
|
+
* - `npm-test-tracker` writes `TEST_PASSED_EVENT` on `npm test`
|
|
14
|
+
* success.
|
|
15
|
+
* - `retest-required-tracker` (this file) writes `RETEST_REQUIRED_EVENT`
|
|
16
|
+
* on `git pull` success \u2014 the workspace
|
|
17
|
+
* just changed; prior test state is stale.
|
|
18
|
+
* - `push-requires-tests` gates via
|
|
19
|
+
* `happened: { event: TEST_PASSED_EVENT, in: "agent_loop",
|
|
20
|
+
* since: RETEST_REQUIRED_EVENT }`
|
|
21
|
+
* which fires when the most-recent `TEST_PASSED_EVENT` is older
|
|
22
|
+
* than the most-recent `RETEST_REQUIRED_EVENT` (or the tests
|
|
23
|
+
* never ran at all in this agent loop).
|
|
24
|
+
*
|
|
25
|
+
* Same encapsulation convention as `npm-test-tracker`: this file
|
|
26
|
+
* owns the `RETEST_REQUIRED_EVENT` constant + `markRetestRequired`
|
|
27
|
+
* helper + the observer, so `defineConfig` type-checks references
|
|
28
|
+
* from downstream rules.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { Observer, ObserverContext, PredicateContext } from "@cad0p/pi-steering";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Session-entry event written when `git pull` succeeds. Rules that
|
|
35
|
+
* previously satisfied `TEST_PASSED_EVENT` become stale relative to
|
|
36
|
+
* this event; `push-requires-tests` compares the two via
|
|
37
|
+
* `happened.since` to require a re-run after any pull.
|
|
38
|
+
*/
|
|
39
|
+
export const RETEST_REQUIRED_EVENT = "example-retest-required" as const;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Payload shape. Minimal \u2014 we just need a presence marker with a
|
|
43
|
+
* timestamp (engine-attached via `findEntries`).
|
|
44
|
+
*/
|
|
45
|
+
export interface RetestRequiredPayload {
|
|
46
|
+
/** Which pull command triggered the invalidation. Debug-only. */
|
|
47
|
+
command: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Record a "retest required" session entry. Callable from an observer
|
|
52
|
+
* or a rule's `onFire` \u2014 both receive a ctx with `appendEntry`.
|
|
53
|
+
*/
|
|
54
|
+
export function markRetestRequired(
|
|
55
|
+
ctx: ObserverContext | PredicateContext,
|
|
56
|
+
payload: RetestRequiredPayload = { command: "git pull" },
|
|
57
|
+
): void {
|
|
58
|
+
ctx.appendEntry<RetestRequiredPayload>(RETEST_REQUIRED_EVENT, payload);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The observer. `as const satisfies Observer` preserves the literal
|
|
63
|
+
* `writes` tuple so `defineConfig` threads `RETEST_REQUIRED_EVENT`
|
|
64
|
+
* into the `AllWrites` union \u2014 letting rules reference it from
|
|
65
|
+
* `when.happened.since` with a compile-time typo check.
|
|
66
|
+
*/
|
|
67
|
+
export const retestRequiredTracker = {
|
|
68
|
+
name: "retest-required-tracker",
|
|
69
|
+
writes: [RETEST_REQUIRED_EVENT],
|
|
70
|
+
watch: {
|
|
71
|
+
toolName: "bash",
|
|
72
|
+
inputMatches: { command: /^git\s+pull\b/ },
|
|
73
|
+
exitCode: "success",
|
|
74
|
+
},
|
|
75
|
+
onResult: (event, ctx) => {
|
|
76
|
+
const input = event.input as { command?: string } | undefined;
|
|
77
|
+
markRetestRequired(ctx, {
|
|
78
|
+
command: input?.command ?? "git pull",
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
} as const satisfies Observer;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Part of the pi-steering work-item-plugin example.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unit tests for the `workItemFormat` predicate.
|
|
6
|
+
*
|
|
7
|
+
* Uses `testPredicate` from `@cad0p/pi-steering/testing` — the lightest
|
|
8
|
+
* possible way to exercise a predicate: construct a `PredicateContext`,
|
|
9
|
+
* call the handler, get the boolean verdict.
|
|
10
|
+
*
|
|
11
|
+
* We do NOT spin up a full harness here; `index.test.ts` covers the
|
|
12
|
+
* end-to-end path. Per-file unit tests focus on the one thing the file
|
|
13
|
+
* owns.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { describe, it } from "node:test";
|
|
18
|
+
import type { Word } from "@cad0p/pi-steering";
|
|
19
|
+
import { testPredicate } from "@cad0p/pi-steering/testing";
|
|
20
|
+
import { workItemFormat } from "./work-item-format.ts";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tiny Word factory. Real Words carry `pos` / `end` / `parts` too, but
|
|
24
|
+
* the predicate only ever reads `.value`; omitting the rest keeps the
|
|
25
|
+
* test concise.
|
|
26
|
+
*/
|
|
27
|
+
function W(value: string): Word {
|
|
28
|
+
return { value, text: value, pos: 0, end: value.length } as Word;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("workItemFormat", () => {
|
|
32
|
+
it("matches when the -m value contains the pattern", async () => {
|
|
33
|
+
const fires = await testPredicate(
|
|
34
|
+
workItemFormat,
|
|
35
|
+
{ pattern: /\[PROJ-\d+\]/ },
|
|
36
|
+
{
|
|
37
|
+
input: {
|
|
38
|
+
tool: "bash",
|
|
39
|
+
command: 'git commit -m "feat: [PROJ-42] subject"',
|
|
40
|
+
basename: "git",
|
|
41
|
+
args: [W("commit"), W("-m"), W("feat: [PROJ-42] subject")],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
assert.equal(fires, true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("does NOT match when the -m value lacks the pattern", async () => {
|
|
49
|
+
const fires = await testPredicate(
|
|
50
|
+
workItemFormat,
|
|
51
|
+
{ pattern: /\[PROJ-\d+\]/ },
|
|
52
|
+
{
|
|
53
|
+
input: {
|
|
54
|
+
tool: "bash",
|
|
55
|
+
command: 'git commit -m "feat: subject only"',
|
|
56
|
+
basename: "git",
|
|
57
|
+
args: [W("commit"), W("-m"), W("feat: subject only")],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
assert.equal(fires, false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("supports the --message long form", async () => {
|
|
65
|
+
const fires = await testPredicate(
|
|
66
|
+
workItemFormat,
|
|
67
|
+
{ pattern: /\[PROJ-\d+\]/ },
|
|
68
|
+
{
|
|
69
|
+
input: {
|
|
70
|
+
tool: "bash",
|
|
71
|
+
command: 'git commit --message "[PROJ-1] fix"',
|
|
72
|
+
basename: "git",
|
|
73
|
+
args: [W("commit"), W("--message"), W("[PROJ-1] fix")],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
assert.equal(fires, true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("preserves quote-aware value through -m (no whitespace munging)", async () => {
|
|
81
|
+
// Real-world-ish: a message with internal spaces. The Word's
|
|
82
|
+
// .value gives us the unwrapped literal — we don't have to
|
|
83
|
+
// un-quote ourselves.
|
|
84
|
+
const fires = await testPredicate(
|
|
85
|
+
workItemFormat,
|
|
86
|
+
{ pattern: /\[PROJ-\d+\]/ },
|
|
87
|
+
{
|
|
88
|
+
input: {
|
|
89
|
+
tool: "bash",
|
|
90
|
+
command:
|
|
91
|
+
'git commit -m "longer subject with spaces and [PROJ-7] mid-sentence"',
|
|
92
|
+
basename: "git",
|
|
93
|
+
args: [
|
|
94
|
+
W("commit"),
|
|
95
|
+
W("-m"),
|
|
96
|
+
W("longer subject with spaces and [PROJ-7] mid-sentence"),
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
assert.equal(fires, true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("falls back to command string when no -m flag is found in args", async () => {
|
|
105
|
+
// Rule author wrote a `workItemFormat` predicate on a command
|
|
106
|
+
// whose args don't carry `-m` (e.g. `git commit` with no -m).
|
|
107
|
+
// Fallback matches against `input.command`.
|
|
108
|
+
const fires = await testPredicate(
|
|
109
|
+
workItemFormat,
|
|
110
|
+
{ pattern: /\[PROJ-\d+\]/ },
|
|
111
|
+
{
|
|
112
|
+
input: {
|
|
113
|
+
tool: "bash",
|
|
114
|
+
command: "git commit",
|
|
115
|
+
basename: "git",
|
|
116
|
+
args: [W("commit")],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
// No `-m`, and `"git commit"` has no PROJ token — does not fire.
|
|
121
|
+
assert.equal(fires, false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("returns false when args and command are both absent", async () => {
|
|
125
|
+
const fires = await testPredicate(
|
|
126
|
+
workItemFormat,
|
|
127
|
+
{
|
|
128
|
+
pattern: /\[PROJ-\d+\]/,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
input: { tool: "bash" },
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
assert.equal(fires, false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("returns false when the arg shape is malformed", async () => {
|
|
138
|
+
// A rule author who passed a plain string instead of
|
|
139
|
+
// { pattern }. Fail-closed: predicate returns false rather than
|
|
140
|
+
// throwing.
|
|
141
|
+
const fires = await testPredicate(
|
|
142
|
+
workItemFormat,
|
|
143
|
+
// Intentionally wrong shape — widened via `as unknown` so the
|
|
144
|
+
// test isn't a type error (the predicate itself handles bad
|
|
145
|
+
// args defensively).
|
|
146
|
+
"not-an-object" as unknown as { pattern: RegExp },
|
|
147
|
+
{
|
|
148
|
+
input: {
|
|
149
|
+
tool: "bash",
|
|
150
|
+
command: 'git commit -m "[PROJ-1] x"',
|
|
151
|
+
args: [W("commit"), W("-m"), W("[PROJ-1] x")],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
assert.equal(fires, false);
|
|
156
|
+
});
|
|
157
|
+
});
|