@miphamai/cli 0.11.0 → 0.13.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.
@@ -3052,6 +3052,223 @@ Permission level is controlled by /config permission <level>.`,
3052
3052
  // Security / Audit — security review checklist
3053
3053
  // ═══════════════════════════════════════════════════════════════
3054
3054
 
3055
+ const promptAuditCmd: CommandHandler = async () => {
3056
+ const { existsSync, readFileSync, readdirSync, statSync } = await import('node:fs')
3057
+ const { join, extname, basename } = await import('node:path')
3058
+ const cwd = process.cwd()
3059
+
3060
+ // Patterns that suggest prompts written for older/less-capable models
3061
+ const AUDIT_RULES: Array<{
3062
+ id: string
3063
+ pattern: RegExp
3064
+ severity: 'low' | 'medium' | 'high'
3065
+ message: string
3066
+ suggestion: string
3067
+ }> = [
3068
+ {
3069
+ id: 'over-explaining',
3070
+ pattern:
3071
+ /you are (a|an) (helpful |friendly |knowledgeable )?(AI |language model |assistant)/i,
3072
+ severity: 'low',
3073
+ message: 'Self-identification preamble ("You are a...")',
3074
+ suggestion: "Modern models don't need role preamble. Remove or shorten to 1 line.",
3075
+ },
3076
+ {
3077
+ id: 'step-by-step',
3078
+ pattern: /(think|reason|work) (step[ -]?by[ -]?step|through this|carefully about this)/i,
3079
+ severity: 'low',
3080
+ message: 'Step-by-step reasoning prompt',
3081
+ suggestion: 'Reasoning models handle this natively. Remove for token savings.',
3082
+ },
3083
+ {
3084
+ id: 'do-not-list',
3085
+ pattern:
3086
+ /(do not|never|you must not|don't|under no circumstances).*\n.*(do not|never|you must not|don't)/i,
3087
+ severity: 'medium',
3088
+ message: 'Multiple "do not" constraints',
3089
+ suggestion: 'Modern models need fewer prohibitions. Consolidate to 1-2 key constraints.',
3090
+ },
3091
+ {
3092
+ id: 'token-waste',
3093
+ pattern:
3094
+ /(remember|keep in mind|it is important to note|please note that|i want to emphasize)/i,
3095
+ severity: 'low',
3096
+ message: 'Filler emphasis phrases ("remember", "please note")',
3097
+ suggestion: 'Remove filler. State the instruction directly.',
3098
+ },
3099
+ {
3100
+ id: 'format-over-spec',
3101
+ pattern: /(you must (always |only )?respond (in|with|using) (JSON|XML|YAML|markdown|HTML))/i,
3102
+ severity: 'low',
3103
+ message: 'Over-specified output format',
3104
+ suggestion: 'Modern models follow format instructions with fewer words. Shorten.',
3105
+ },
3106
+ {
3107
+ id: 'example-overload',
3108
+ pattern: /((?:example|for instance|e\.g\.,).*\n){3,}/i,
3109
+ severity: 'low',
3110
+ message: 'Three or more examples in sequence',
3111
+ suggestion: '1-2 examples suffice for modern models. Remove redundant ones.',
3112
+ },
3113
+ {
3114
+ id: 'old-model-ref',
3115
+ pattern: /(GPT-3|GPT-3\.5|Claude\s*[12][^3]|text-davinci|command-r|llama\s*2)/i,
3116
+ severity: 'high',
3117
+ message: 'Reference to older model',
3118
+ suggestion: 'Update model references to current generation (Sonnet 5, Opus 5, GPT-5, etc.).',
3119
+ },
3120
+ {
3121
+ id: 'long-preamble',
3122
+ pattern: /^.{200,}$/m,
3123
+ severity: 'medium',
3124
+ message: 'Very long single-line instructions (200+ chars)',
3125
+ suggestion: 'Break into bullet points. Modern models parse structured prompts better.',
3126
+ },
3127
+ {
3128
+ id: 'verbose-constraint',
3129
+ pattern:
3130
+ /you (must|should|shall) (always |only |never )?(ensure|guarantee|verify|validate|confirm|cross-check|double-check)/i,
3131
+ severity: 'medium',
3132
+ message: 'Overly verbose constraint language',
3133
+ suggestion: 'Replace "you must ensure that" with imperative: "Ensure".',
3134
+ },
3135
+ ]
3136
+
3137
+ // Files to scan
3138
+ const scanDirs = [
3139
+ join(cwd, '.mipham', 'skills'),
3140
+ join(cwd, '.mipham', 'rules'),
3141
+ join(cwd, '.claude'),
3142
+ ]
3143
+ const skillFiles: string[] = []
3144
+ // Also check standard project files
3145
+ const projectFiles = [join(cwd, 'CLAUDE.md'), join(cwd, 'MIPHAM.md'), join(cwd, 'PRODUCT.md')]
3146
+
3147
+ // Collect skill/rules files
3148
+ for (const dir of scanDirs) {
3149
+ try {
3150
+ if (!existsSync(dir)) continue
3151
+ const walk = (d: string) => {
3152
+ const entries = readdirSync(d)
3153
+ for (const e of entries) {
3154
+ const fp = join(d, e)
3155
+ const st = statSync(fp)
3156
+ if (st.isDirectory()) {
3157
+ walk(fp)
3158
+ continue
3159
+ }
3160
+ if (['.md', '.SKILL.md', '.mipham-skill.md', '.txt'].some((ext) => fp.endsWith(ext))) {
3161
+ skillFiles.push(fp)
3162
+ }
3163
+ }
3164
+ }
3165
+ walk(dir)
3166
+ } catch {
3167
+ /* skip */
3168
+ }
3169
+ }
3170
+
3171
+ const allFiles = [...new Set([...projectFiles.filter((f) => existsSync(f)), ...skillFiles])]
3172
+
3173
+ if (allFiles.length === 0) {
3174
+ return {
3175
+ content: [
3176
+ '🔍 Prompt Audit',
3177
+ '',
3178
+ 'No prompt files found to audit.',
3179
+ 'Place skills in .mipham/skills/ and rules in .mipham/rules/.',
3180
+ ].join('\n'),
3181
+ }
3182
+ }
3183
+
3184
+ // Scan each file
3185
+ const results: Array<{
3186
+ file: string
3187
+ line: number
3188
+ ruleId: string
3189
+ severity: string
3190
+ message: string
3191
+ suggestion: string
3192
+ }> = []
3193
+
3194
+ for (const fp of allFiles) {
3195
+ try {
3196
+ const content = readFileSync(fp, 'utf-8')
3197
+ const lines = content.split('\n')
3198
+ for (const rule of AUDIT_RULES) {
3199
+ // Multi-line pattern: check full content
3200
+ if (rule.id === 'do-not-list' || rule.id === 'example-overload') {
3201
+ if (rule.pattern.test(content)) {
3202
+ // Find approx line
3203
+ const match = rule.pattern.exec(content)
3204
+ const idx = match ? content.slice(0, match.index).split('\n').length : 1
3205
+ results.push({
3206
+ file: fp.replace(cwd + '/', ''),
3207
+ line: idx,
3208
+ ruleId: rule.id,
3209
+ severity: rule.severity,
3210
+ message: rule.message,
3211
+ suggestion: rule.suggestion,
3212
+ })
3213
+ }
3214
+ continue
3215
+ }
3216
+
3217
+ // Line-by-line patterns
3218
+ for (let i = 0; i < lines.length; i++) {
3219
+ const line = lines[i]!
3220
+ if (rule.pattern.test(line)) {
3221
+ results.push({
3222
+ file: fp.replace(cwd + '/', ''),
3223
+ line: i + 1,
3224
+ ruleId: rule.id,
3225
+ severity: rule.severity,
3226
+ message: rule.message,
3227
+ suggestion: rule.suggestion,
3228
+ })
3229
+ }
3230
+ }
3231
+ }
3232
+ } catch {
3233
+ /* skip unreadable files */
3234
+ }
3235
+ }
3236
+
3237
+ // Format output
3238
+ const severityIcon: Record<string, string> = { high: '🔴', medium: '🟡', low: '🟢' }
3239
+ const grouped: Record<string, typeof results> = {}
3240
+ for (const r of results) {
3241
+ const key = r.file
3242
+ if (!grouped[key]) grouped[key] = []
3243
+ grouped[key]!.push(r)
3244
+ }
3245
+
3246
+ const output: string[] = [
3247
+ `🔍 Prompt Audit — ${allFiles.length} file(s) scanned, ${results.length} finding(s)`,
3248
+ '',
3249
+ ]
3250
+
3251
+ if (results.length === 0) {
3252
+ output.push('✅ All prompts look optimized for modern models. No issues found.')
3253
+ } else {
3254
+ for (const [file, items] of Object.entries(grouped)) {
3255
+ output.push(`📄 ${file} (${items!.length} finding${items!.length > 1 ? 's' : ''})`)
3256
+ for (const item of items!) {
3257
+ output.push(
3258
+ ` ${severityIcon[item.severity] || '⚪'} L${item.line}: [${item.ruleId}] ${item.message}`,
3259
+ )
3260
+ output.push(` → ${item.suggestion}`)
3261
+ }
3262
+ output.push('')
3263
+ }
3264
+ }
3265
+
3266
+ output.push('─'.repeat(60))
3267
+ output.push('Scanned: ' + allFiles.map((f) => basename(f)).join(', '))
3268
+
3269
+ return { content: output.join('\n') }
3270
+ }
3271
+
3055
3272
  const securityCmd: CommandHandler = async () => {
3056
3273
  const findings: string[] = []
3057
3274
  const ok: string[] = []
@@ -3272,7 +3489,8 @@ const ideCmd: CommandHandler = async (_ctx) => {
3272
3489
  // ═══════════════════════════════════════════════════════════════
3273
3490
 
3274
3491
  const terminalSetupCmd: CommandHandler = async () => {
3275
- const { writeFileSync, appendFileSync, existsSync, mkdirSync } = await import('node:fs')
3492
+ const { writeFileSync, appendFileSync, existsSync, mkdirSync, readFileSync } =
3493
+ await import('node:fs')
3276
3494
  const { join } = await import('node:path')
3277
3495
  const { homedir } = await import('node:os')
3278
3496
 
@@ -3313,9 +3531,7 @@ const terminalSetupCmd: CommandHandler = async () => {
3313
3531
  const sourceLine = `\n# Mipham Code shell integration\n[ -f ~/.mipham/shell-setup.sh ] && source ~/.mipham/shell-setup.sh\n`
3314
3532
 
3315
3533
  try {
3316
- const existing = existsSync(profilePath)
3317
- ? require('node:fs').readFileSync(profilePath, 'utf-8')
3318
- : ''
3534
+ const existing = existsSync(profilePath) ? readFileSync(profilePath, 'utf-8') : ''
3319
3535
  if (existing.includes('shell-setup.sh')) {
3320
3536
  lines.push(` ⏭ ${profileName} already has Mipham Code integration`)
3321
3537
  } else {
@@ -3692,6 +3908,128 @@ Run \`mipham agents\` for the interactive dashboard.`,
3692
3908
  // Artifacts
3693
3909
  // ═══════════════════════════════════════════════════════════════
3694
3910
 
3911
+ const forkCmd: CommandHandler = async (ctx, args) => {
3912
+ const prompt = args.join(' ')
3913
+ if (!prompt.trim()) {
3914
+ return {
3915
+ content: [
3916
+ 'Usage: /fork <task description>',
3917
+ '',
3918
+ 'Fork a task into an isolated worktree with auto commit/push.',
3919
+ '',
3920
+ 'Workflow:',
3921
+ ' 1. Creates a new git worktree on branch worktree/<slug>',
3922
+ ' 2. Spawns a background agent to work on the task',
3923
+ ' 3. Auto-commits changes with Co-Authored-By on completion',
3924
+ ' 4. Auto-pushes to origin',
3925
+ ' 5. Reports branch and worktree path in the result',
3926
+ '',
3927
+ 'Examples:',
3928
+ ' /fork Refactor the auth module',
3929
+ ' /fork Add input validation to all API endpoints',
3930
+ ].join('\n'),
3931
+ }
3932
+ }
3933
+
3934
+ const { execSync } = await import('node:child_process')
3935
+ const { join } = await import('node:path')
3936
+
3937
+ try {
3938
+ execSync('git rev-parse --git-dir', { stdio: 'ignore' })
3939
+ } catch {
3940
+ return { content: '/fork requires a git repository.' }
3941
+ }
3942
+
3943
+ const agentViewManager = ctx.engine.getAgentViewManager?.()
3944
+ if (!agentViewManager) {
3945
+ return { content: 'Agent View manager is initializing...' }
3946
+ }
3947
+
3948
+ const slug = prompt
3949
+ .toLowerCase()
3950
+ .replace(/[^a-z0-9]+/g, '-')
3951
+ .replace(/^-|-$/g, '')
3952
+ .slice(0, 40)
3953
+ const name = `${slug}-${Date.now().toString(36)}`
3954
+ const branch = `worktree/${name}`
3955
+ const wtPath = join(process.cwd(), '.claude', 'worktrees', name)
3956
+
3957
+ try {
3958
+ execSync(`git worktree add -b ${branch} ${wtPath} HEAD`, { stdio: 'ignore', timeout: 30_000 })
3959
+ } catch (err) {
3960
+ try {
3961
+ execSync(`git worktree remove --force ${wtPath}`, { stdio: 'ignore' })
3962
+ } catch {
3963
+ /* ok */
3964
+ }
3965
+ try {
3966
+ execSync(`git branch -D ${branch}`, { stdio: 'ignore' })
3967
+ } catch {
3968
+ /* ok */
3969
+ }
3970
+ return {
3971
+ content: `Worktree creation failed: ${err instanceof Error ? err.message : String(err)}`,
3972
+ }
3973
+ }
3974
+
3975
+ const session = agentViewManager.create(prompt, prompt, {
3976
+ provider: ctx.providerId,
3977
+ model: ctx.modelId,
3978
+ })
3979
+ agentViewManager.addMessage(session.id, { role: 'user', content: prompt })
3980
+ agentViewManager.updateStatus(session.id, 'working')
3981
+ session.worktree = wtPath
3982
+ session.branch = branch
3983
+ session.kind = 'forked'
3984
+
3985
+ const bgReg = (await import('../agent/background-registry')).getBackgroundAgentRegistry()
3986
+ bgReg.spawn(
3987
+ prompt,
3988
+ 'general',
3989
+ async (_signal) => {
3990
+ const { SubAgent } = await import('../agent/sub-agent')
3991
+ const sa = new SubAgent(ctx.engine.getRegistry(), ctx.engine.getTools())
3992
+ const result = await sa.execute(prompt, 'fork: ' + prompt.slice(0, 60), {})
3993
+ try {
3994
+ const { execSync: ex } = await import('node:child_process')
3995
+ ex(`git -C ${wtPath} add -A`, { stdio: 'ignore', timeout: 10_000 })
3996
+ const st = ex(`git -C ${wtPath} status --porcelain`, { encoding: 'utf-8', timeout: 10_000 })
3997
+ if (st.trim()) {
3998
+ ex(
3999
+ `git -C ${wtPath} commit -m "feat: ${prompt.slice(0, 60)}\n\nCo-Authored-By: Claude <noreply@anthropic.com>"`,
4000
+ { stdio: 'ignore', timeout: 10_000 },
4001
+ )
4002
+ ex(`git push origin ${branch}`, { stdio: 'ignore', timeout: 30_000 })
4003
+ }
4004
+ } catch {
4005
+ /* best-effort */
4006
+ }
4007
+ return [
4008
+ `## Fork done: ${prompt}`,
4009
+ '',
4010
+ `Branch: \`${branch}\``,
4011
+ `Worktree: \`${wtPath}\``,
4012
+ '',
4013
+ result || '(no output)',
4014
+ ].join('\n')
4015
+ },
4016
+ 'forked',
4017
+ )
4018
+
4019
+ return {
4020
+ content: [
4021
+ `✓ Forked: ${session.id}`,
4022
+ ` Task: ${prompt.slice(0, 80)}`,
4023
+ ` Branch: ${branch}`,
4024
+ ` Worktree: ${wtPath}`,
4025
+ ` Status: working`,
4026
+ '',
4027
+ 'Agent will auto-commit + push on completion.',
4028
+ 'Use /agents to track.',
4029
+ ].join('\n'),
4030
+ }
4031
+ }
4032
+
3695
4033
  async function openBrowser(url: string): Promise<void> {
3696
4034
  const cmd =
3697
4035
  process.platform === 'darwin'
@@ -3858,6 +4196,7 @@ const commandsListCmd: CommandHandler = () => {
3858
4196
  '/recommend': 'Project',
3859
4197
  '/security': 'Project',
3860
4198
  '/audit': 'Project',
4199
+ '/prompt-audit': 'Code Quality',
3861
4200
  '/ide': 'Environment',
3862
4201
  '/terminal-setup': 'Environment',
3863
4202
  '/memory': 'Environment',
@@ -3867,6 +4206,7 @@ const commandsListCmd: CommandHandler = () => {
3867
4206
  '/feedback': 'Account',
3868
4207
  '/agents': 'Agents',
3869
4208
  '/bg': 'Agents',
4209
+ '/fork': 'Agents',
3870
4210
  '/artifact': 'Artifacts',
3871
4211
  '/schedule': 'Other',
3872
4212
  '/commit': 'Workflow',
@@ -3997,6 +4337,7 @@ registry.set('/permissions', permissionsCmd)
3997
4337
  registry.set('/add-dir', addDirCmd)
3998
4338
  registry.set('/security', securityCmd)
3999
4339
  registry.set('/audit', securityCmd)
4340
+ registry.set('/prompt-audit', promptAuditCmd)
4000
4341
 
4001
4342
  // Environment
4002
4343
  registry.set('/theme', themeCmd)
@@ -4011,6 +4352,7 @@ registry.set('/logout', logoutCmd)
4011
4352
  registry.set('/feedback', feedbackCmd)
4012
4353
  registry.set('/agents', agentsCmd)
4013
4354
  registry.set('/bg', bgCmd)
4355
+ registry.set('/fork', forkCmd)
4014
4356
 
4015
4357
  // Artifacts
4016
4358
  registry.set('/artifact', artifactBaseCmd)
@@ -4120,6 +4462,7 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
4120
4462
  '/recommend': 'Analyze project + recommend skills & setup',
4121
4463
  '/security': 'Security review checklist',
4122
4464
  '/audit': 'Same as /security',
4465
+ '/prompt-audit': 'Audit prompts for modern model optimization',
4123
4466
  '/ide': 'IDE integration guide',
4124
4467
  '/terminal-setup': 'Shell & terminal config',
4125
4468
  '/memory': 'Manage AI memories',
@@ -4130,6 +4473,7 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
4130
4473
  '/feedback': 'Send feedback',
4131
4474
  '/agents': 'Agent view dashboard',
4132
4475
  '/bg': 'Run a background agent task',
4476
+ '/fork': 'Fork task into isolated worktree',
4133
4477
  '/artifact': 'Manage artifacts',
4134
4478
  '/schedule': 'View scheduled tasks and cron jobs',
4135
4479
  '/commit': 'Generate commit message and review staged changes',
package/dist/mipham DELETED
Binary file