pumuki 6.3.73 → 6.3.76
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/VERSION +1 -1
- package/docs/README.md +9 -7
- package/docs/operations/RELEASE_NOTES.md +0 -18
- package/docs/validation/README.md +3 -1
- package/integrations/evidence/buildEvidence.ts +14 -0
- package/integrations/evidence/repoState.ts +3 -0
- package/integrations/evidence/schema.ts +18 -0
- package/integrations/evidence/trackingContract.ts +146 -0
- package/integrations/evidence/writeEvidence.ts +14 -0
- package/integrations/gate/evaluateAiGate.ts +166 -3
- package/integrations/gate/governanceActionCatalog.ts +45 -0
- package/integrations/gate/remediationCatalog.ts +8 -0
- package/integrations/git/GitService.ts +0 -25
- package/integrations/git/aiGateRepoPolicyFindings.ts +4 -0
- package/integrations/git/runPlatformGateFacts.ts +0 -1
- package/integrations/lifecycle/adapter.templates.json +0 -3
- package/integrations/lifecycle/adapter.ts +24 -0
- package/integrations/lifecycle/bootstrapManifest.ts +248 -0
- package/integrations/lifecycle/cli.ts +30 -68
- package/integrations/lifecycle/cliSdd.ts +4 -3
- package/integrations/lifecycle/doctor.ts +7 -22
- package/integrations/lifecycle/governanceObservationSnapshot.ts +29 -2
- package/integrations/lifecycle/index.ts +0 -2
- package/integrations/lifecycle/install.ts +21 -0
- package/integrations/lifecycle/state.ts +8 -1
- package/integrations/mcp/aiGateCheck.ts +140 -10
- package/integrations/mcp/alignedPlatformGate.ts +232 -0
- package/integrations/mcp/autoExecuteAiStart.ts +6 -1
- package/integrations/mcp/enterpriseServer.ts +23 -7
- package/integrations/mcp/enterpriseStdioServer.cli.ts +32 -5
- package/integrations/mcp/preFlightCheck.ts +10 -0
- package/integrations/mcp/readMcpPrePushStdin.ts +7 -0
- package/integrations/platform/detectPlatforms.ts +0 -37
- package/integrations/policy/experimentalFeatures.ts +1 -1
- package/package.json +1 -10
- package/scripts/consumer-postinstall.cjs +1 -10
- package/AGENTS.md +0 -269
- package/CHANGELOG.md +0 -686
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +0 -62
- package/integrations/lifecycle/audit.ts +0 -101
- package/scripts/consumer-postinstall-resolve-args.cjs +0 -38
- package/scripts/pumuki-full-surface-smoke-lib.ts +0 -37
- package/scripts/pumuki-full-surface-smoke.ts +0 -261
- package/scripts/pumuki-smoke-installed-wrapper.cjs +0 -31
|
@@ -6,6 +6,10 @@ const AI_GATE_STAGES = new Set<AiGateStage>(['PRE_WRITE', 'PRE_COMMIT', 'PRE_PUS
|
|
|
6
6
|
|
|
7
7
|
const REPO_POLICY_CODES = new Set<string>([
|
|
8
8
|
'GITFLOW_PROTECTED_BRANCH',
|
|
9
|
+
'GITFLOW_BRANCH_NAMING_INVALID',
|
|
10
|
+
'TRACKING_CANONICAL_SOURCE_CONFLICT',
|
|
11
|
+
'TRACKING_CANONICAL_FILE_MISSING',
|
|
12
|
+
'TRACKING_CANONICAL_IN_PROGRESS_INVALID',
|
|
9
13
|
'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT',
|
|
10
14
|
'EVIDENCE_PREWRITE_WORKTREE_WARN',
|
|
11
15
|
]);
|
|
@@ -27,7 +27,6 @@ export type GateScope =
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const DEFAULT_EXTENSIONS = ['.swift', '.ts', '.tsx', '.js', '.jsx', '.kt', '.kts'];
|
|
30
|
-
export const DEFAULT_FACT_FILE_EXTENSIONS = DEFAULT_EXTENSIONS;
|
|
31
30
|
|
|
32
31
|
export const countScannedFilesFromFacts = (facts: ReadonlyArray<Fact>): number => {
|
|
33
32
|
const contentPaths = new Set<string>();
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"codex": [
|
|
3
3
|
{
|
|
4
4
|
"path": ".pumuki/adapter.json",
|
|
5
|
-
"mode": "json-merge",
|
|
6
5
|
"payload": {
|
|
7
6
|
"hooks": {
|
|
8
7
|
"pre_write": {
|
|
@@ -32,7 +31,6 @@
|
|
|
32
31
|
"repo": [
|
|
33
32
|
{
|
|
34
33
|
"path": ".pumuki/adapter.json",
|
|
35
|
-
"mode": "json-merge",
|
|
36
34
|
"payload": {
|
|
37
35
|
"hooks": {
|
|
38
36
|
"pre_write": {
|
|
@@ -145,7 +143,6 @@
|
|
|
145
143
|
},
|
|
146
144
|
{
|
|
147
145
|
"path": ".pumuki/adapter.json",
|
|
148
|
-
"mode": "json-merge",
|
|
149
146
|
"payload": {
|
|
150
147
|
"hooks": {
|
|
151
148
|
"pre_write": {
|
|
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import { LifecycleGitService, type ILifecycleGitService } from './gitService';
|
|
5
|
+
import { writeLifecycleBootstrapManifest } from './bootstrapManifest';
|
|
5
6
|
|
|
6
7
|
export type AdapterAgent = string;
|
|
7
8
|
|
|
@@ -11,6 +12,10 @@ export type LifecycleAdapterInstallResult = {
|
|
|
11
12
|
dryRun: boolean;
|
|
12
13
|
written: boolean;
|
|
13
14
|
changedFiles: ReadonlyArray<string>;
|
|
15
|
+
bootstrapManifest: {
|
|
16
|
+
path: string;
|
|
17
|
+
changed: boolean;
|
|
18
|
+
};
|
|
14
19
|
};
|
|
15
20
|
|
|
16
21
|
type AdapterTemplate = {
|
|
@@ -154,11 +159,30 @@ export const runLifecycleAdapterInstall = (params: {
|
|
|
154
159
|
writeFileSync(absolutePath, nextContents, 'utf8');
|
|
155
160
|
}
|
|
156
161
|
|
|
162
|
+
let bootstrapManifest = {
|
|
163
|
+
path: join(repoRoot, '.pumuki', 'bootstrap-manifest.json'),
|
|
164
|
+
changed: false,
|
|
165
|
+
};
|
|
166
|
+
if (!dryRun) {
|
|
167
|
+
const manifestWrite = writeLifecycleBootstrapManifest({
|
|
168
|
+
git,
|
|
169
|
+
repoRoot,
|
|
170
|
+
});
|
|
171
|
+
bootstrapManifest = {
|
|
172
|
+
path: manifestWrite.path,
|
|
173
|
+
changed: manifestWrite.changed,
|
|
174
|
+
};
|
|
175
|
+
if (manifestWrite.changed) {
|
|
176
|
+
changedFiles.push('.pumuki/bootstrap-manifest.json');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
157
180
|
return {
|
|
158
181
|
repoRoot,
|
|
159
182
|
agent: params.agent,
|
|
160
183
|
dryRun,
|
|
161
184
|
written: !dryRun,
|
|
162
185
|
changedFiles,
|
|
186
|
+
bootstrapManifest,
|
|
163
187
|
};
|
|
164
188
|
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager'
|
|
4
|
+
import { LifecycleGitService, type ILifecycleGitService } from './gitService'
|
|
5
|
+
import {
|
|
6
|
+
readGovernanceObservationSnapshot,
|
|
7
|
+
type GovernanceContractSurface,
|
|
8
|
+
type GovernanceObservationSnapshot,
|
|
9
|
+
} from './governanceObservationSnapshot'
|
|
10
|
+
import { readGovernanceNextAction, type GovernanceNextActionSummary } from './governanceNextAction'
|
|
11
|
+
import { getCurrentPumukiPackageName, getCurrentPumukiVersion } from './packageInfo'
|
|
12
|
+
import { readLifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot'
|
|
13
|
+
import { readLifecyclePolicyValidationSnapshot } from './policyValidationSnapshot'
|
|
14
|
+
import { readLifecycleState } from './state'
|
|
15
|
+
|
|
16
|
+
export const BOOTSTRAP_MANIFEST_RELATIVE_PATH = '.pumuki/bootstrap-manifest.json'
|
|
17
|
+
|
|
18
|
+
type AdapterCommandContract = {
|
|
19
|
+
path: string
|
|
20
|
+
present: boolean
|
|
21
|
+
hooks: {
|
|
22
|
+
pre_write?: string
|
|
23
|
+
pre_commit?: string
|
|
24
|
+
pre_push?: string
|
|
25
|
+
ci?: string
|
|
26
|
+
}
|
|
27
|
+
mcp: {
|
|
28
|
+
enterprise?: string
|
|
29
|
+
evidence?: string
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type LifecycleBootstrapManifest = {
|
|
34
|
+
schema_version: '1'
|
|
35
|
+
repo_root: string
|
|
36
|
+
package: {
|
|
37
|
+
name: string
|
|
38
|
+
version: string
|
|
39
|
+
}
|
|
40
|
+
lifecycle: {
|
|
41
|
+
installed: boolean
|
|
42
|
+
version: string | null
|
|
43
|
+
installed_at: string | null
|
|
44
|
+
managed_hooks: ReadonlyArray<string>
|
|
45
|
+
openspec_managed_artifacts: ReadonlyArray<string>
|
|
46
|
+
}
|
|
47
|
+
hooks_directory: {
|
|
48
|
+
path: string
|
|
49
|
+
source: 'git-rev-parse' | 'git-config' | 'default'
|
|
50
|
+
}
|
|
51
|
+
hook_status: Record<string, { managed_block_present: boolean; exists: boolean }>
|
|
52
|
+
contract_surface: GovernanceContractSurface
|
|
53
|
+
governance: {
|
|
54
|
+
effective: GovernanceObservationSnapshot['governance_effective']
|
|
55
|
+
attention_codes: ReadonlyArray<string>
|
|
56
|
+
next_action: GovernanceNextActionSummary
|
|
57
|
+
bootstrap_hints: ReadonlyArray<string>
|
|
58
|
+
}
|
|
59
|
+
sdd: {
|
|
60
|
+
effective_mode: GovernanceObservationSnapshot['sdd']['effective_mode']
|
|
61
|
+
experimental_source: string
|
|
62
|
+
session_active: boolean
|
|
63
|
+
session_valid: boolean
|
|
64
|
+
change_id: string | null
|
|
65
|
+
}
|
|
66
|
+
policy_strict: GovernanceObservationSnapshot['policy_strict']
|
|
67
|
+
git: GovernanceObservationSnapshot['git']
|
|
68
|
+
adapter: AdapterCommandContract
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type LifecycleBootstrapManifestWriteResult = {
|
|
72
|
+
path: string
|
|
73
|
+
changed: boolean
|
|
74
|
+
manifest: LifecycleBootstrapManifest
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
78
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
79
|
+
|
|
80
|
+
const readOptionalCommand = (source: unknown): string | undefined => {
|
|
81
|
+
if (!isRecord(source)) {
|
|
82
|
+
return undefined
|
|
83
|
+
}
|
|
84
|
+
const command = source.command
|
|
85
|
+
return typeof command === 'string' && command.trim().length > 0 ? command.trim() : undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const readAdapterCommandContract = (repoRoot: string): AdapterCommandContract => {
|
|
89
|
+
const path = join(repoRoot, '.pumuki', 'adapter.json')
|
|
90
|
+
if (!existsSync(path)) {
|
|
91
|
+
return {
|
|
92
|
+
path: BOOTSTRAP_MANIFEST_RELATIVE_PATH.replace('bootstrap-manifest.json', 'adapter.json'),
|
|
93
|
+
present: false,
|
|
94
|
+
hooks: {},
|
|
95
|
+
mcp: {},
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown
|
|
101
|
+
const hooks = isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {}
|
|
102
|
+
const mcp = isRecord(parsed) && isRecord(parsed.mcp) ? parsed.mcp : {}
|
|
103
|
+
return {
|
|
104
|
+
path: '.pumuki/adapter.json',
|
|
105
|
+
present: true,
|
|
106
|
+
hooks: {
|
|
107
|
+
pre_write: readOptionalCommand(isRecord(hooks) ? hooks.pre_write : undefined),
|
|
108
|
+
pre_commit: readOptionalCommand(isRecord(hooks) ? hooks.pre_commit : undefined),
|
|
109
|
+
pre_push: readOptionalCommand(isRecord(hooks) ? hooks.pre_push : undefined),
|
|
110
|
+
ci: readOptionalCommand(isRecord(hooks) ? hooks.ci : undefined),
|
|
111
|
+
},
|
|
112
|
+
mcp: {
|
|
113
|
+
enterprise: readOptionalCommand(isRecord(mcp) ? mcp.enterprise : undefined),
|
|
114
|
+
evidence: readOptionalCommand(isRecord(mcp) ? mcp.evidence : undefined),
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
return {
|
|
119
|
+
path: '.pumuki/adapter.json',
|
|
120
|
+
present: false,
|
|
121
|
+
hooks: {},
|
|
122
|
+
mcp: {},
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const parseManagedHooks = (raw?: string): string[] => {
|
|
128
|
+
if (typeof raw !== 'string' || raw.trim().length === 0) {
|
|
129
|
+
return []
|
|
130
|
+
}
|
|
131
|
+
return raw
|
|
132
|
+
.split(',')
|
|
133
|
+
.map((value) => value.trim())
|
|
134
|
+
.filter((value) => value.length > 0)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const parseManagedArtifacts = (raw?: string): string[] => {
|
|
138
|
+
if (typeof raw !== 'string' || raw.trim().length === 0) {
|
|
139
|
+
return []
|
|
140
|
+
}
|
|
141
|
+
return raw
|
|
142
|
+
.split(',')
|
|
143
|
+
.map((value) => value.trim())
|
|
144
|
+
.filter((value) => value.length > 0)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const toHookStatusSummary = (
|
|
148
|
+
hookStatus: ReturnType<typeof getPumukiHooksStatus>
|
|
149
|
+
): Record<string, { managed_block_present: boolean; exists: boolean }> =>
|
|
150
|
+
Object.fromEntries(
|
|
151
|
+
Object.entries(hookStatus).map(([hook, entry]) => [
|
|
152
|
+
hook,
|
|
153
|
+
{
|
|
154
|
+
managed_block_present: entry.managedBlockPresent,
|
|
155
|
+
exists: entry.exists,
|
|
156
|
+
},
|
|
157
|
+
])
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
export const buildLifecycleBootstrapManifest = (params: {
|
|
161
|
+
repoRoot: string
|
|
162
|
+
git?: ILifecycleGitService
|
|
163
|
+
}): LifecycleBootstrapManifest => {
|
|
164
|
+
const git = params.git ?? new LifecycleGitService()
|
|
165
|
+
const repoRoot = git.resolveRepoRoot(params.repoRoot)
|
|
166
|
+
const lifecycleState = readLifecycleState(git, repoRoot)
|
|
167
|
+
const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot()
|
|
168
|
+
const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot)
|
|
169
|
+
const governanceObservation = readGovernanceObservationSnapshot({
|
|
170
|
+
repoRoot,
|
|
171
|
+
experimentalFeatures,
|
|
172
|
+
policyValidation,
|
|
173
|
+
git,
|
|
174
|
+
})
|
|
175
|
+
const governanceNextAction = readGovernanceNextAction({
|
|
176
|
+
repoRoot,
|
|
177
|
+
stage: 'PRE_WRITE',
|
|
178
|
+
governanceObservation,
|
|
179
|
+
})
|
|
180
|
+
const hooksDirectory = resolvePumukiHooksDirectory(repoRoot)
|
|
181
|
+
const hookStatus = getPumukiHooksStatus(repoRoot)
|
|
182
|
+
const adapter = readAdapterCommandContract(repoRoot)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
schema_version: '1',
|
|
186
|
+
repo_root: repoRoot,
|
|
187
|
+
package: {
|
|
188
|
+
name: getCurrentPumukiPackageName(),
|
|
189
|
+
version: getCurrentPumukiVersion({ repoRoot }),
|
|
190
|
+
},
|
|
191
|
+
lifecycle: {
|
|
192
|
+
installed: lifecycleState.installed === 'true',
|
|
193
|
+
version: typeof lifecycleState.version === 'string' && lifecycleState.version.length > 0
|
|
194
|
+
? lifecycleState.version
|
|
195
|
+
: null,
|
|
196
|
+
installed_at:
|
|
197
|
+
typeof lifecycleState.installedAt === 'string' && lifecycleState.installedAt.length > 0
|
|
198
|
+
? lifecycleState.installedAt
|
|
199
|
+
: null,
|
|
200
|
+
managed_hooks: parseManagedHooks(lifecycleState.hooks),
|
|
201
|
+
openspec_managed_artifacts: parseManagedArtifacts(lifecycleState.openSpecManagedArtifacts),
|
|
202
|
+
},
|
|
203
|
+
hooks_directory: hooksDirectory,
|
|
204
|
+
hook_status: toHookStatusSummary(hookStatus),
|
|
205
|
+
contract_surface: governanceObservation.contract_surface,
|
|
206
|
+
governance: {
|
|
207
|
+
effective: governanceObservation.governance_effective,
|
|
208
|
+
attention_codes: governanceObservation.attention_codes,
|
|
209
|
+
next_action: governanceNextAction,
|
|
210
|
+
bootstrap_hints: governanceObservation.agent_bootstrap_hints,
|
|
211
|
+
},
|
|
212
|
+
sdd: {
|
|
213
|
+
effective_mode: governanceObservation.sdd.effective_mode,
|
|
214
|
+
experimental_source: governanceObservation.sdd.experimental_source,
|
|
215
|
+
session_active: governanceObservation.sdd_session.active,
|
|
216
|
+
session_valid: governanceObservation.sdd_session.valid,
|
|
217
|
+
change_id: governanceObservation.sdd_session.change_id,
|
|
218
|
+
},
|
|
219
|
+
policy_strict: governanceObservation.policy_strict,
|
|
220
|
+
git: governanceObservation.git,
|
|
221
|
+
adapter,
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const serializeManifest = (manifest: LifecycleBootstrapManifest): string =>
|
|
226
|
+
`${JSON.stringify(manifest, null, 2)}\n`
|
|
227
|
+
|
|
228
|
+
export const writeLifecycleBootstrapManifest = (params: {
|
|
229
|
+
repoRoot: string
|
|
230
|
+
git?: ILifecycleGitService
|
|
231
|
+
}): LifecycleBootstrapManifestWriteResult => {
|
|
232
|
+
const git = params.git ?? new LifecycleGitService()
|
|
233
|
+
const repoRoot = git.resolveRepoRoot(params.repoRoot)
|
|
234
|
+
const path = join(repoRoot, BOOTSTRAP_MANIFEST_RELATIVE_PATH)
|
|
235
|
+
const manifest = buildLifecycleBootstrapManifest({ repoRoot, git })
|
|
236
|
+
const nextContents = serializeManifest(manifest)
|
|
237
|
+
const currentContents = existsSync(path) ? readFileSync(path, 'utf8') : ''
|
|
238
|
+
const changed = currentContents !== nextContents
|
|
239
|
+
if (changed) {
|
|
240
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
241
|
+
writeFileSync(path, nextContents, 'utf8')
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
path,
|
|
245
|
+
changed,
|
|
246
|
+
manifest,
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -77,7 +77,6 @@ import {
|
|
|
77
77
|
type RemoteCiDiagnostics,
|
|
78
78
|
} from './remoteCiDiagnostics';
|
|
79
79
|
import { runPolicyReconcile } from './policyReconcile';
|
|
80
|
-
import { runLifecycleAudit, type LifecycleAuditStage } from './audit';
|
|
81
80
|
import { resolvePreWriteEnforcement, type PreWriteEnforcementResolution } from '../policy/preWriteEnforcement';
|
|
82
81
|
|
|
83
82
|
type LifecycleCommand =
|
|
@@ -93,8 +92,7 @@ type LifecycleCommand =
|
|
|
93
92
|
| 'sdd'
|
|
94
93
|
| 'adapter'
|
|
95
94
|
| 'analytics'
|
|
96
|
-
| 'policy'
|
|
97
|
-
| 'audit';
|
|
95
|
+
| 'policy';
|
|
98
96
|
|
|
99
97
|
type SddCommand =
|
|
100
98
|
| 'status'
|
|
@@ -180,8 +178,6 @@ export type ParsedArgs = {
|
|
|
180
178
|
policyCommand?: PolicyCommand;
|
|
181
179
|
policyStrict?: boolean;
|
|
182
180
|
policyApply?: boolean;
|
|
183
|
-
auditStage?: LifecycleAuditStage;
|
|
184
|
-
auditEngine?: boolean;
|
|
185
181
|
};
|
|
186
182
|
|
|
187
183
|
const HELP_TEXT = `
|
|
@@ -192,7 +188,6 @@ Pumuki lifecycle commands:
|
|
|
192
188
|
pumuki remove
|
|
193
189
|
pumuki update [--latest|--spec=<package-spec>]
|
|
194
190
|
pumuki doctor [--remote-checks] [--deep] [--parity] [--json]
|
|
195
|
-
pumuki audit [--stage=PRE_COMMIT|PRE_PUSH|CI] [--engine] [--json]
|
|
196
191
|
pumuki status [--json] [--remote-checks]
|
|
197
192
|
pumuki watch [--stage=PRE_COMMIT|PRE_PUSH|CI] [--scope=workingTree|staged|repoAndStaged|repo] [--severity=critical|high|medium|low] [--interval-ms=<n>] [--notify-cooldown-ms=<n>] [--no-notify] [--once|--iterations=<n>] [--json]
|
|
198
193
|
pumuki loop run --objective=<text> [--max-attempts=<n>] [--json]
|
|
@@ -238,8 +233,7 @@ const isLifecycleCommand = (value: string): value is LifecycleCommand =>
|
|
|
238
233
|
value === 'sdd' ||
|
|
239
234
|
value === 'adapter' ||
|
|
240
235
|
value === 'analytics' ||
|
|
241
|
-
value === 'policy'
|
|
242
|
-
value === 'audit';
|
|
236
|
+
value === 'policy';
|
|
243
237
|
|
|
244
238
|
const parseAdapterAgent = (value?: string): AdapterAgent => {
|
|
245
239
|
const normalized = (value ?? '').trim();
|
|
@@ -269,16 +263,6 @@ const parseSddStage = (value?: string): SddStage => {
|
|
|
269
263
|
throw new Error(`Unsupported SDD stage "${value}". Use PRE_WRITE, PRE_COMMIT, PRE_PUSH or CI.`);
|
|
270
264
|
};
|
|
271
265
|
|
|
272
|
-
const parseAuditStage = (value?: string): LifecycleAuditStage => {
|
|
273
|
-
const stage = parseSddStage(value);
|
|
274
|
-
if (stage === 'PRE_WRITE') {
|
|
275
|
-
throw new Error(
|
|
276
|
-
'PRE_WRITE is not supported for "pumuki audit". Use PRE_COMMIT, PRE_PUSH or CI (aliases GREEN, REFACTOR, CLOSE).'
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
return stage;
|
|
280
|
-
};
|
|
281
|
-
|
|
282
266
|
const parseSddEvidencePath = (value: string): string => {
|
|
283
267
|
const normalized = value.trim();
|
|
284
268
|
if (normalized.length === 0) {
|
|
@@ -644,8 +628,6 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
644
628
|
let analyticsSinceDays: ParsedArgs['analyticsSinceDays'];
|
|
645
629
|
let analyticsJsonOutputPath: ParsedArgs['analyticsJsonOutputPath'];
|
|
646
630
|
let analyticsMarkdownOutputPath: ParsedArgs['analyticsMarkdownOutputPath'];
|
|
647
|
-
let auditStage: LifecycleAuditStage | undefined;
|
|
648
|
-
let auditEngine = false;
|
|
649
631
|
|
|
650
632
|
if (commandRaw === 'watch') {
|
|
651
633
|
for (const arg of argv.slice(1)) {
|
|
@@ -829,31 +811,6 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
829
811
|
};
|
|
830
812
|
}
|
|
831
813
|
|
|
832
|
-
if (commandRaw === 'audit') {
|
|
833
|
-
for (const arg of argv.slice(1)) {
|
|
834
|
-
if (arg === '--json') {
|
|
835
|
-
json = true;
|
|
836
|
-
continue;
|
|
837
|
-
}
|
|
838
|
-
if (arg === '--engine') {
|
|
839
|
-
auditEngine = true;
|
|
840
|
-
continue;
|
|
841
|
-
}
|
|
842
|
-
if (arg.startsWith('--stage=')) {
|
|
843
|
-
auditStage = parseAuditStage(arg.slice('--stage='.length));
|
|
844
|
-
continue;
|
|
845
|
-
}
|
|
846
|
-
throw new Error(`Unsupported argument "${arg}".\n\n${HELP_TEXT}`);
|
|
847
|
-
}
|
|
848
|
-
return {
|
|
849
|
-
command: commandRaw,
|
|
850
|
-
purgeArtifacts: false,
|
|
851
|
-
json,
|
|
852
|
-
auditStage: auditStage ?? 'PRE_COMMIT',
|
|
853
|
-
...(auditEngine ? { auditEngine: true } : {}),
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
|
|
857
814
|
if (commandRaw === 'loop') {
|
|
858
815
|
const subcommandRaw = argv[1] ?? '';
|
|
859
816
|
if (
|
|
@@ -1662,8 +1619,8 @@ export type PreWriteOpenSpecBootstrapTrace = {
|
|
|
1662
1619
|
details?: string;
|
|
1663
1620
|
};
|
|
1664
1621
|
|
|
1665
|
-
export const
|
|
1666
|
-
'PUMUKI_EXPERIMENTAL_PRE_WRITE=
|
|
1622
|
+
export const PRE_WRITE_ENABLE_STRICT_COMMAND =
|
|
1623
|
+
'PUMUKI_EXPERIMENTAL_PRE_WRITE=strict npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json';
|
|
1667
1624
|
export const buildSddExperimentalEnableAdvisoryCommand = (stage: SddStage): string =>
|
|
1668
1625
|
`PUMUKI_EXPERIMENTAL_SDD=advisory npx --yes --package pumuki@latest pumuki sdd validate --stage=${stage} --json`;
|
|
1669
1626
|
const buildAnalyticsExperimentalEnableCommand = (action: AnalyticsHotspotsCommand): string =>
|
|
@@ -1757,6 +1714,7 @@ const buildSaasIngestionExperimentalDisabledEnvelope = (
|
|
|
1757
1714
|
export const buildPreWriteExperimentalDisabledResult = (params: {
|
|
1758
1715
|
stage: SddStage;
|
|
1759
1716
|
status: SddEvaluateResult['status'];
|
|
1717
|
+
source: 'env' | 'legacy-env' | 'default';
|
|
1760
1718
|
}): SddEvaluateResult => ({
|
|
1761
1719
|
stage: params.stage,
|
|
1762
1720
|
status: params.status,
|
|
@@ -1764,14 +1722,16 @@ export const buildPreWriteExperimentalDisabledResult = (params: {
|
|
|
1764
1722
|
allowed: true,
|
|
1765
1723
|
code: 'PRE_WRITE_EXPERIMENTAL_DISABLED',
|
|
1766
1724
|
message:
|
|
1767
|
-
'PRE_WRITE
|
|
1725
|
+
'PRE_WRITE está desactivado explícitamente. Reactívalo con PUMUKI_EXPERIMENTAL_PRE_WRITE=strict si necesitas recuperar el gate previo a escritura.',
|
|
1768
1726
|
details: {
|
|
1769
1727
|
experimental: true,
|
|
1770
|
-
default_off:
|
|
1728
|
+
default_off: false,
|
|
1729
|
+
disabled_explicitly: true,
|
|
1730
|
+
disabled_source: params.source,
|
|
1771
1731
|
layer: 'experimental',
|
|
1772
1732
|
activation_env: 'PUMUKI_EXPERIMENTAL_PRE_WRITE',
|
|
1773
1733
|
legacy_activation_env: 'PUMUKI_PREWRITE_ENFORCEMENT',
|
|
1774
|
-
activation_command:
|
|
1734
|
+
activation_command: PRE_WRITE_ENABLE_STRICT_COMMAND,
|
|
1775
1735
|
},
|
|
1776
1736
|
},
|
|
1777
1737
|
});
|
|
@@ -2153,6 +2113,10 @@ export const runLifecycleCli = async (
|
|
|
2153
2113
|
repo_root: installResult.repoRoot,
|
|
2154
2114
|
version: installResult.version,
|
|
2155
2115
|
hooks_changed: installResult.changedHooks,
|
|
2116
|
+
bootstrap_manifest: {
|
|
2117
|
+
path: installResult.bootstrapManifest.path,
|
|
2118
|
+
changed: installResult.bootstrapManifest.changed,
|
|
2119
|
+
},
|
|
2156
2120
|
openspec: installResult.openSpecBootstrap
|
|
2157
2121
|
? {
|
|
2158
2122
|
installed: installResult.openSpecBootstrap.packageInstalled,
|
|
@@ -2165,6 +2129,10 @@ export const runLifecycleCli = async (
|
|
|
2165
2129
|
mcp: {
|
|
2166
2130
|
agent: adapterResult.agent,
|
|
2167
2131
|
changed_files: adapterResult.changedFiles,
|
|
2132
|
+
bootstrap_manifest: {
|
|
2133
|
+
path: adapterResult.bootstrapManifest.path,
|
|
2134
|
+
changed: adapterResult.bootstrapManifest.changed,
|
|
2135
|
+
},
|
|
2168
2136
|
adapter_health: adapterCheck
|
|
2169
2137
|
? {
|
|
2170
2138
|
status: adapterCheck.status,
|
|
@@ -2191,6 +2159,9 @@ export const runLifecycleCli = async (
|
|
|
2191
2159
|
writeInfo(
|
|
2192
2160
|
`[pumuki] bootstrap install: hooks changed=${installResult.changedHooks.join(', ') || 'none'}`
|
|
2193
2161
|
);
|
|
2162
|
+
writeInfo(
|
|
2163
|
+
`[pumuki] bootstrap manifest: path=${installResult.bootstrapManifest.path} changed=${installResult.bootstrapManifest.changed ? 'yes' : 'no'}`
|
|
2164
|
+
);
|
|
2194
2165
|
if (installResult.openSpecBootstrap) {
|
|
2195
2166
|
writeInfo(
|
|
2196
2167
|
`[pumuki] bootstrap openspec: installed=${installResult.openSpecBootstrap.packageInstalled ? 'yes' : 'no'} project=${installResult.openSpecBootstrap.projectInitialized ? 'yes' : 'no'} actions=${installResult.openSpecBootstrap.actions.join(', ') || 'none'}`
|
|
@@ -2231,6 +2202,9 @@ export const runLifecycleCli = async (
|
|
|
2231
2202
|
writeInfo(
|
|
2232
2203
|
`[pumuki] installed ${result.version} at ${result.repoRoot} (hooks changed: ${result.changedHooks.join(', ') || 'none'})`
|
|
2233
2204
|
);
|
|
2205
|
+
writeInfo(
|
|
2206
|
+
`[pumuki] bootstrap manifest: path=${result.bootstrapManifest.path} changed=${result.bootstrapManifest.changed ? 'yes' : 'no'}`
|
|
2207
|
+
);
|
|
2234
2208
|
if (result.openSpecBootstrap) {
|
|
2235
2209
|
writeInfo(
|
|
2236
2210
|
`[pumuki] openspec bootstrap: installed=${result.openSpecBootstrap.packageInstalled ? 'yes' : 'no'} project=${result.openSpecBootstrap.projectInitialized ? 'yes' : 'no'} actions=${result.openSpecBootstrap.actions.join(', ') || 'none'}`
|
|
@@ -2246,6 +2220,9 @@ export const runLifecycleCli = async (
|
|
|
2246
2220
|
writeInfo(
|
|
2247
2221
|
`[pumuki] mcp wiring: agent=${adapterResult.agent} changed=${adapterResult.changedFiles.length}`
|
|
2248
2222
|
);
|
|
2223
|
+
writeInfo(
|
|
2224
|
+
`[pumuki] mcp manifest: path=${adapterResult.bootstrapManifest.path} changed=${adapterResult.bootstrapManifest.changed ? 'yes' : 'no'}`
|
|
2225
|
+
);
|
|
2249
2226
|
if (adapterResult.changedFiles.length > 0) {
|
|
2250
2227
|
writeInfo(`[pumuki] mcp files: ${adapterResult.changedFiles.join(', ')}`);
|
|
2251
2228
|
}
|
|
@@ -2302,24 +2279,6 @@ export const runLifecycleCli = async (
|
|
|
2302
2279
|
);
|
|
2303
2280
|
return 0;
|
|
2304
2281
|
}
|
|
2305
|
-
case 'audit': {
|
|
2306
|
-
const result = await runLifecycleAudit({
|
|
2307
|
-
stage: parsed.auditStage ?? 'PRE_COMMIT',
|
|
2308
|
-
auditMode: parsed.auditEngine === true ? 'engine' : 'gate',
|
|
2309
|
-
});
|
|
2310
|
-
if (parsed.json) {
|
|
2311
|
-
writeInfo(JSON.stringify(result, null, 2));
|
|
2312
|
-
} else {
|
|
2313
|
-
writeInfo(
|
|
2314
|
-
`[pumuki] audit: repo=${result.repo_root} stage=${result.stage} mode=${result.audit_mode} exit=${result.gate_exit_code}`
|
|
2315
|
-
);
|
|
2316
|
-
writeInfo(
|
|
2317
|
-
`[pumuki] audit: files_scanned=${result.files_scanned ?? 'n/a'} untracked_matching_extensions=${result.untracked_matching_extensions_count} outcome=${result.snapshot_outcome ?? 'n/a'}`
|
|
2318
|
-
);
|
|
2319
|
-
writeInfo(`[pumuki] audit: hint=${result.policy_reconcile_hint}`);
|
|
2320
|
-
}
|
|
2321
|
-
return result.gate_exit_code;
|
|
2322
|
-
}
|
|
2323
2282
|
case 'doctor': {
|
|
2324
2283
|
const report = runLifecycleDoctor({
|
|
2325
2284
|
deep: parsed.doctorDeep === true,
|
|
@@ -2772,6 +2731,9 @@ export const runLifecycleCli = async (
|
|
|
2772
2731
|
`[pumuki] adapter files: ${result.changedFiles.join(', ')}`
|
|
2773
2732
|
);
|
|
2774
2733
|
}
|
|
2734
|
+
writeInfo(
|
|
2735
|
+
`[pumuki] adapter manifest: path=${result.bootstrapManifest.path} changed=${result.bootstrapManifest.changed ? 'yes' : 'no'}`
|
|
2736
|
+
);
|
|
2775
2737
|
}
|
|
2776
2738
|
return 0;
|
|
2777
2739
|
}
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
buildPreWriteExperimentalDisabledResult,
|
|
37
37
|
buildSddExperimentalEnableAdvisoryCommand,
|
|
38
38
|
runRawPreWriteAiGateCheck,
|
|
39
|
-
|
|
39
|
+
PRE_WRITE_ENABLE_STRICT_COMMAND,
|
|
40
40
|
} from './cli';
|
|
41
41
|
|
|
42
42
|
export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: LifecycleCliDependencies): Promise<number> => {
|
|
@@ -84,6 +84,7 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
|
|
|
84
84
|
const disabledResult = buildPreWriteExperimentalDisabledResult({
|
|
85
85
|
stage: requestedStage,
|
|
86
86
|
status: readSddStatus(process.cwd()),
|
|
87
|
+
source: preWriteEnforcement.source,
|
|
87
88
|
});
|
|
88
89
|
if (parsed.json) {
|
|
89
90
|
writeInfo(
|
|
@@ -106,7 +107,7 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
|
|
|
106
107
|
},
|
|
107
108
|
next_action: {
|
|
108
109
|
reason: 'PRE_WRITE_EXPERIMENTAL_DISABLED',
|
|
109
|
-
command:
|
|
110
|
+
command: PRE_WRITE_ENABLE_STRICT_COMMAND,
|
|
110
111
|
},
|
|
111
112
|
},
|
|
112
113
|
null,
|
|
@@ -127,7 +128,7 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
|
|
|
127
128
|
`[pumuki][sdd] pre-write enforcement: mode=${preWriteEnforcement.mode} source=${preWriteEnforcement.source} blocking=no`
|
|
128
129
|
);
|
|
129
130
|
writeInfo(
|
|
130
|
-
`[pumuki][sdd] next action (PRE_WRITE_EXPERIMENTAL_DISABLED): ${
|
|
131
|
+
`[pumuki][sdd] next action (PRE_WRITE_EXPERIMENTAL_DISABLED): ${PRE_WRITE_ENABLE_STRICT_COMMAND}`
|
|
131
132
|
);
|
|
132
133
|
}
|
|
133
134
|
return 0;
|
|
@@ -113,7 +113,6 @@ export type LifecycleDoctorReport = {
|
|
|
113
113
|
experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
|
|
114
114
|
governanceObservation: GovernanceObservationSnapshot;
|
|
115
115
|
governanceNextAction: GovernanceNextActionSummary;
|
|
116
|
-
policy_signature_remediation?: string;
|
|
117
116
|
issues: ReadonlyArray<DoctorIssue>;
|
|
118
117
|
deep?: DoctorDeepReport;
|
|
119
118
|
parity_profile?: DoctorParityProfile;
|
|
@@ -811,15 +810,6 @@ const compareDoctorParityProfile = (params: {
|
|
|
811
810
|
};
|
|
812
811
|
};
|
|
813
812
|
|
|
814
|
-
const buildPolicySignatureRemediation = (
|
|
815
|
-
policyValidation: LifecyclePolicyValidationSnapshot
|
|
816
|
-
): string | undefined => {
|
|
817
|
-
const mismatch = Object.values(policyValidation.stages).some(
|
|
818
|
-
(stage) => stage.validationCode === 'POLICY_AS_CODE_SIGNATURE_MISMATCH'
|
|
819
|
-
);
|
|
820
|
-
return mismatch ? 'pumuki policy reconcile --apply' : undefined;
|
|
821
|
-
};
|
|
822
|
-
|
|
823
813
|
export const runLifecycleDoctor = (params?: {
|
|
824
814
|
cwd?: string;
|
|
825
815
|
git?: ILifecycleGitService;
|
|
@@ -879,8 +869,6 @@ export const runLifecycleDoctor = (params?: {
|
|
|
879
869
|
? compareDoctorParityProfile({ repoRoot, actual: parity_profile })
|
|
880
870
|
: undefined;
|
|
881
871
|
|
|
882
|
-
const policySignatureRemediation = buildPolicySignatureRemediation(policyValidation);
|
|
883
|
-
|
|
884
872
|
return {
|
|
885
873
|
repoRoot,
|
|
886
874
|
packageVersion: version.effective,
|
|
@@ -892,11 +880,8 @@ export const runLifecycleDoctor = (params?: {
|
|
|
892
880
|
hooksDirectoryResolution: hooksDirectory.source,
|
|
893
881
|
policyValidation,
|
|
894
882
|
experimentalFeatures,
|
|
895
|
-
governanceObservation,
|
|
896
883
|
governanceNextAction,
|
|
897
|
-
|
|
898
|
-
? { policy_signature_remediation: policySignatureRemediation }
|
|
899
|
-
: {}),
|
|
884
|
+
governanceObservation,
|
|
900
885
|
issues,
|
|
901
886
|
deep,
|
|
902
887
|
parity_profile,
|
|
@@ -914,11 +899,11 @@ export const doctorHasGovernanceAttention = (report: LifecycleDoctorReport): boo
|
|
|
914
899
|
doctorGovernanceNeedsAttention(report.governanceObservation);
|
|
915
900
|
|
|
916
901
|
export const doctorCommandShouldWarnHuman = (report: LifecycleDoctorReport): boolean =>
|
|
917
|
-
report.issues.length > 0
|
|
918
|
-
report.deep?.checks.some((check) => check.status !== 'pass') === true
|
|
919
|
-
doctorHasGovernanceAttention(report);
|
|
902
|
+
report.issues.length > 0
|
|
903
|
+
|| report.deep?.checks.some((check) => check.status !== 'pass') === true
|
|
904
|
+
|| doctorHasGovernanceAttention(report);
|
|
920
905
|
|
|
921
906
|
export const doctorCommandShouldFailExit = (report: LifecycleDoctorReport): boolean =>
|
|
922
|
-
doctorHasBlockingIssues(report)
|
|
923
|
-
doctorHasParityMismatch(report)
|
|
924
|
-
doctorGovernanceIsBlocking(report.governanceObservation);
|
|
907
|
+
doctorHasBlockingIssues(report)
|
|
908
|
+
|| doctorHasParityMismatch(report)
|
|
909
|
+
|| doctorGovernanceIsBlocking(report.governanceObservation);
|